|
@@ -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");
|