* "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
        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 */
 
 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);
        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)
 {
        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
  *
  * 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.
        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);
 }
 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");