summaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/u_serial.c
diff options
context:
space:
mode:
authorSebastian Andrzej Siewior <bigeasy@linutronix.de>2012-12-23 21:10:06 +0100
committerFelipe Balbi <balbi@ti.com>2013-01-21 20:52:43 +0200
commit19b10a8828a6cdd5a4e7e37babd5084d35641f87 (patch)
tree636a228ed32d975a8214639b17b4d7a595fdf7b4 /drivers/usb/gadget/u_serial.c
parentb473577854fea63055ff9ab84f0f52a3e8aed15e (diff)
downloadblackbird-op-linux-19b10a8828a6cdd5a4e7e37babd5084d35641f87.tar.gz
blackbird-op-linux-19b10a8828a6cdd5a4e7e37babd5084d35641f87.zip
usb: gadget: allocate & giveback serial ports instead hard code them
This patch removes gserial_setup() and gserial_cleanup() and adds gserial_alloc_line() and gserial_free_line() to replace them. The initial setup of u_serial happens now on module load time. A maximum of four TTY ports can be requested which is the current limit. In theory we could extend this limit, the hard limit is the number of available endpoints. alloc_tty_driver() is now called at module init time with the max available ports. The per-line footprint here is on 32bit is 3 * size of pointer + 60 bytes (for cdevs). The remaining memory (struct gs_port) is allocated once a port is requested. With this change it is possible to load g_multi and g_serial at the same time. GS0 receives the module that is loaded first, GS1 is received by the next module and so on. With the configfs interface the port number can be exported and the device node is more predictable. Nothing changes for g_serial and friends as long as one module is used. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb/gadget/u_serial.c')
-rw-r--r--drivers/usb/gadget/u_serial.c310
1 files changed, 150 insertions, 160 deletions
diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c
index 1662d839a1d6..ea360fda2e14 100644
--- a/drivers/usb/gadget/u_serial.c
+++ b/drivers/usb/gadget/u_serial.c
@@ -36,11 +36,12 @@
* "serial port" functionality through the USB gadget stack. Each such
* port is exposed through a /dev/ttyGS* node.
*
- * After initialization (gserial_setup), these TTY port devices stay
- * available until they are removed (gserial_cleanup). Each one may be
- * connected to a USB function (gserial_connect), or disconnected (with
- * gserial_disconnect) when the USB host issues a config change event.
- * Data can only flow when the port is connected to the host.
+ * After this module has been loaded, the individual TTY port can be requested
+ * (gserial_alloc_line()) and it will stay available until they are removed
+ * (gserial_free_line()). Each one may be connected to a USB function
+ * (gserial_connect), or disconnected (with gserial_disconnect) when the USB
+ * host issues a config change event. Data can only flow when the port is
+ * connected to the host.
*
* A given TTY port can be made available in multiple configurations.
* For example, each one might expose a ttyGS0 node which provides a
@@ -120,13 +121,10 @@ struct gs_port {
struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */
};
-/* increase N_PORTS if you need more */
-#define N_PORTS 4
static struct portmaster {
struct mutex lock; /* protect open/close */
struct gs_port *port;
-} ports[N_PORTS];
-static unsigned n_ports;
+} ports[MAX_U_SERIAL_PORTS];
#define GS_CLOSE_TIMEOUT 15 /* seconds */
@@ -1033,10 +1031,19 @@ static int
gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
{
struct gs_port *port;
+ int ret = 0;
+
+ mutex_lock(&ports[port_num].lock);
+ if (ports[port_num].port) {
+ ret = -EBUSY;
+ goto out;
+ }
port = kzalloc(sizeof(struct gs_port), GFP_KERNEL);
- if (port == NULL)
- return -ENOMEM;
+ if (port == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
tty_port_init(&port->port);
spin_lock_init(&port->port_lock);
@@ -1052,114 +1059,10 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
port->port_line_coding = *coding;
ports[port_num].port = port;
-
- return 0;
-}
-
-/**
- * gserial_setup - initialize TTY driver for one or more ports
- * @g: gadget to associate with these ports
- * @count: how many ports to support
- * Context: may sleep
- *
- * The TTY stack needs to know in advance how many devices it should
- * plan to manage. Use this call to set up the ports you will be
- * exporting through USB. Later, connect them to functions based
- * on what configuration is activated by the USB host; and disconnect
- * them as appropriate.
- *
- * An example would be a two-configuration device in which both
- * configurations expose port 0, but through different functions.
- * One configuration could even expose port 1 while the other
- * one doesn't.
- *
- * Returns negative errno or zero.
- */
-int gserial_setup(struct usb_gadget *g, unsigned count)
-{
- unsigned i;
- struct usb_cdc_line_coding coding;
- int status;
-
- if (count == 0 || count > N_PORTS)
- return -EINVAL;
-
- if (gs_tty_driver)
- return -EBUSY;
-
- gs_tty_driver = alloc_tty_driver(count);
- if (!gs_tty_driver)
- return -ENOMEM;
-
- gs_tty_driver->driver_name = "g_serial";
- gs_tty_driver->name = PREFIX;
- /* uses dynamically assigned dev_t values */
-
- gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
- gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
- gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
- gs_tty_driver->init_termios = tty_std_termios;
-
- /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
- * MS-Windows. Otherwise, most of these flags shouldn't affect
- * anything unless we were to actually hook up to a serial line.
- */
- gs_tty_driver->init_termios.c_cflag =
- B9600 | CS8 | CREAD | HUPCL | CLOCAL;
- gs_tty_driver->init_termios.c_ispeed = 9600;
- gs_tty_driver->init_termios.c_ospeed = 9600;
-
- coding.dwDTERate = cpu_to_le32(9600);
- coding.bCharFormat = 8;
- coding.bParityType = USB_CDC_NO_PARITY;
- coding.bDataBits = USB_CDC_1_STOP_BITS;
-
- tty_set_operations(gs_tty_driver, &gs_tty_ops);
-
- /* make devices be openable */
- for (i = 0; i < count; i++) {
- mutex_init(&ports[i].lock);
- status = gs_port_alloc(i, &coding);
- if (status) {
- count = i;
- goto fail;
- }
- }
- n_ports = count;
-
- /* export the driver ... */
- status = tty_register_driver(gs_tty_driver);
- if (status) {
- pr_err("%s: cannot register, err %d\n",
- __func__, status);
- goto fail;
- }
-
- /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
- for (i = 0; i < count; i++) {
- struct device *tty_dev;
-
- tty_dev = tty_port_register_device(&ports[i].port->port,
- gs_tty_driver, i, &g->dev);
- if (IS_ERR(tty_dev))
- pr_warning("%s: no classdev for port %d, err %ld\n",
- __func__, i, PTR_ERR(tty_dev));
- }
-
- pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
- count, (count == 1) ? "" : "s");
-
- return status;
-fail:
- while (count--) {
- tty_port_destroy(&ports[count].port->port);
- kfree(ports[count].port);
- }
- put_tty_driver(gs_tty_driver);
- gs_tty_driver = NULL;
- return status;
+out:
+ mutex_unlock(&ports[port_num].lock);
+ return ret;
}
-EXPORT_SYMBOL_GPL(gserial_setup);
static int gs_closed(struct gs_port *port)
{
@@ -1171,56 +1074,77 @@ static int gs_closed(struct gs_port *port)
return cond;
}
-/**
- * gserial_cleanup - remove TTY-over-USB driver and devices
- * Context: may sleep
- *
- * This is called to free all resources allocated by @gserial_setup().
- * Accordingly, it may need to wait until some open /dev/ files have
- * closed.
- *
- * The caller must have issued @gserial_disconnect() for any ports
- * that had previously been connected, so that there is never any
- * I/O pending when it's called.
- */
-void gserial_cleanup(void)
+static void gserial_free_port(struct gs_port *port)
+{
+ tasklet_kill(&port->push);
+ /* wait for old opens to finish */
+ wait_event(port->port.close_wait, gs_closed(port));
+ WARN_ON(port->port_usb != NULL);
+ tty_port_destroy(&port->port);
+ kfree(port);
+}
+
+void gserial_free_line(unsigned char port_num)
{
- unsigned i;
struct gs_port *port;
- if (!gs_tty_driver)
+ mutex_lock(&ports[port_num].lock);
+ if (WARN_ON(!ports[port_num].port)) {
+ mutex_unlock(&ports[port_num].lock);
return;
+ }
+ port = ports[port_num].port;
+ ports[port_num].port = NULL;
+ mutex_unlock(&ports[port_num].lock);
- /* start sysfs and /dev/ttyGS* node removal */
- for (i = 0; i < n_ports; i++)
- tty_unregister_device(gs_tty_driver, i);
-
- for (i = 0; i < n_ports; i++) {
- /* prevent new opens */
- mutex_lock(&ports[i].lock);
- port = ports[i].port;
- ports[i].port = NULL;
- mutex_unlock(&ports[i].lock);
-
- tasklet_kill(&port->push);
+ gserial_free_port(port);
+ tty_unregister_device(gs_tty_driver, port_num);
+}
+EXPORT_SYMBOL_GPL(gserial_free_line);
- /* wait for old opens to finish */
- wait_event(port->port.close_wait, gs_closed(port));
+int gserial_alloc_line(unsigned char *line_num)
+{
+ struct usb_cdc_line_coding coding;
+ struct device *tty_dev;
+ int ret;
+ int port_num;
- WARN_ON(port->port_usb != NULL);
+ coding.dwDTERate = cpu_to_le32(9600);
+ coding.bCharFormat = 8;
+ coding.bParityType = USB_CDC_NO_PARITY;
+ coding.bDataBits = USB_CDC_1_STOP_BITS;
- tty_port_destroy(&port->port);
- kfree(port);
+ for (port_num = 0; port_num < MAX_U_SERIAL_PORTS; port_num++) {
+ ret = gs_port_alloc(port_num, &coding);
+ if (ret == -EBUSY)
+ continue;
+ if (ret)
+ return ret;
+ break;
}
- n_ports = 0;
+ if (ret)
+ return ret;
- tty_unregister_driver(gs_tty_driver);
- put_tty_driver(gs_tty_driver);
- gs_tty_driver = NULL;
+ /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
- pr_debug("%s: cleaned up ttyGS* support\n", __func__);
+ tty_dev = tty_port_register_device(&ports[port_num].port->port,
+ gs_tty_driver, port_num, NULL);
+ if (IS_ERR(tty_dev)) {
+ struct gs_port *port;
+ pr_err("%s: failed to register tty for port %d, err %ld\n",
+ __func__, port_num, PTR_ERR(tty_dev));
+
+ ret = PTR_ERR(tty_dev);
+ port = ports[port_num].port;
+ ports[port_num].port = NULL;
+ gserial_free_port(port);
+ goto err;
+ }
+ *line_num = port_num;
+err:
+ return ret;
}
-EXPORT_SYMBOL_GPL(gserial_cleanup);
+EXPORT_SYMBOL_GPL(gserial_alloc_line);
/**
* gserial_connect - notify TTY I/O glue that USB link is active
@@ -1237,8 +1161,8 @@ EXPORT_SYMBOL_GPL(gserial_cleanup);
*
* Caller needs to have set up the endpoints and USB function in @dev
* before calling this, as well as the appropriate (speed-specific)
- * endpoint descriptors, and also have set up the TTY driver by calling
- * @gserial_setup().
+ * endpoint descriptors, and also have allocate @port_num by calling
+ * @gserial_alloc_line().
*
* Returns negative errno or zero.
* On success, ep->driver_data will be overwritten.
@@ -1249,11 +1173,18 @@ int gserial_connect(struct gserial *gser, u8 port_num)
unsigned long flags;
int status;
- if (!gs_tty_driver || port_num >= n_ports)
+ if (port_num >= MAX_U_SERIAL_PORTS)
return -ENXIO;
- /* we "know" gserial_cleanup() hasn't been called */
port = ports[port_num].port;
+ if (!port) {
+ pr_err("serial line %d not allocated.\n", port_num);
+ return -EINVAL;
+ }
+ if (port->port_usb) {
+ pr_err("serial line %d is in use.\n", port_num);
+ return -EBUSY;
+ }
/* activate the endpoints */
status = usb_ep_enable(gser->in);
@@ -1357,4 +1288,63 @@ void gserial_disconnect(struct gserial *gser)
}
EXPORT_SYMBOL_GPL(gserial_disconnect);
+int userial_init(void)
+{
+ unsigned i;
+ int status;
+
+ gs_tty_driver = alloc_tty_driver(MAX_U_SERIAL_PORTS);
+ if (!gs_tty_driver)
+ return -ENOMEM;
+
+ gs_tty_driver->driver_name = "g_serial";
+ gs_tty_driver->name = PREFIX;
+ /* uses dynamically assigned dev_t values */
+
+ gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ gs_tty_driver->init_termios = tty_std_termios;
+
+ /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+ * MS-Windows. Otherwise, most of these flags shouldn't affect
+ * anything unless we were to actually hook up to a serial line.
+ */
+ gs_tty_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ gs_tty_driver->init_termios.c_ispeed = 9600;
+ gs_tty_driver->init_termios.c_ospeed = 9600;
+
+ tty_set_operations(gs_tty_driver, &gs_tty_ops);
+ for (i = 0; i < MAX_U_SERIAL_PORTS; i++)
+ mutex_init(&ports[i].lock);
+
+ /* export the driver ... */
+ status = tty_register_driver(gs_tty_driver);
+ if (status) {
+ pr_err("%s: cannot register, err %d\n",
+ __func__, status);
+ goto fail;
+ }
+
+ pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+ MAX_U_SERIAL_PORTS,
+ (MAX_U_SERIAL_PORTS == 1) ? "" : "s");
+
+ return status;
+fail:
+ put_tty_driver(gs_tty_driver);
+ gs_tty_driver = NULL;
+ return status;
+}
+module_init(userial_init);
+
+static void userial_cleanup(void)
+{
+ tty_unregister_driver(gs_tty_driver);
+ put_tty_driver(gs_tty_driver);
+ gs_tty_driver = NULL;
+}
+module_exit(userial_cleanup);
+
MODULE_LICENSE("GPL");
OpenPOWER on IntegriCloud