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>
This commit is contained in:
Sebastian Andrzej Siewior 2012-12-23 21:10:06 +01:00 committed by Felipe Balbi
parent b473577854
commit 19b10a8828
11 changed files with 234 additions and 214 deletions

View File

@ -111,6 +111,7 @@ FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data);
static struct fsg_common fsg_common; static struct fsg_common fsg_common;
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static unsigned char tty_line;
/* /*
* We _always_ have both ACM and mass storage functions. * We _always_ have both ACM and mass storage functions.
@ -125,7 +126,7 @@ static int __init acm_ms_do_config(struct usb_configuration *c)
} }
status = acm_bind_config(c, 0); status = acm_bind_config(c, tty_line);
if (status < 0) if (status < 0)
return status; return status;
@ -152,7 +153,7 @@ static int __init acm_ms_bind(struct usb_composite_dev *cdev)
void *retp; void *retp;
/* set up serial link layer */ /* set up serial link layer */
status = gserial_setup(cdev->gadget, 1); status = gserial_alloc_line(&tty_line);
if (status < 0) if (status < 0)
return status; return status;
@ -188,14 +189,13 @@ static int __init acm_ms_bind(struct usb_composite_dev *cdev)
fail1: fail1:
fsg_common_put(&fsg_common); fsg_common_put(&fsg_common);
fail0: fail0:
gserial_cleanup(); gserial_free_line(tty_line);
return status; return status;
} }
static int __exit acm_ms_unbind(struct usb_composite_dev *cdev) static int __exit acm_ms_unbind(struct usb_composite_dev *cdev)
{ {
gserial_cleanup(); gserial_free_line(tty_line);
return 0; return 0;
} }

View File

@ -108,6 +108,7 @@ static u8 hostaddr[ETH_ALEN];
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static unsigned char tty_line;
/* /*
* We _always_ have both CDC ECM and CDC ACM functions. * We _always_ have both CDC ECM and CDC ACM functions.
*/ */
@ -124,7 +125,7 @@ static int __init cdc_do_config(struct usb_configuration *c)
if (status < 0) if (status < 0)
return status; return status;
status = acm_bind_config(c, 0); status = acm_bind_config(c, tty_line);
if (status < 0) if (status < 0)
return status; return status;
@ -157,7 +158,7 @@ static int __init cdc_bind(struct usb_composite_dev *cdev)
return status; return status;
/* set up serial link layer */ /* set up serial link layer */
status = gserial_setup(cdev->gadget, 1); status = gserial_alloc_line(&tty_line);
if (status < 0) if (status < 0)
goto fail0; goto fail0;
@ -183,7 +184,7 @@ static int __init cdc_bind(struct usb_composite_dev *cdev)
return 0; return 0;
fail1: fail1:
gserial_cleanup(); gserial_free_line(tty_line);
fail0: fail0:
gether_cleanup(); gether_cleanup();
return status; return status;
@ -191,7 +192,7 @@ fail0:
static int __exit cdc_unbind(struct usb_composite_dev *cdev) static int __exit cdc_unbind(struct usb_composite_dev *cdev)
{ {
gserial_cleanup(); gserial_free_line(tty_line);
gether_cleanup(); gether_cleanup();
return 0; return 0;
} }

View File

@ -231,6 +231,10 @@ static void dbgp_unbind(struct usb_gadget *gadget)
gadget->ep0->driver_data = NULL; gadget->ep0->driver_data = NULL;
} }
#ifdef CONFIG_USB_G_DBGP_SERIAL
static unsigned char tty_line;
#endif
static int __init dbgp_configure_endpoints(struct usb_gadget *gadget) static int __init dbgp_configure_endpoints(struct usb_gadget *gadget)
{ {
int stp; int stp;
@ -268,7 +272,7 @@ static int __init dbgp_configure_endpoints(struct usb_gadget *gadget)
dbgp.serial->in->desc = &i_desc; dbgp.serial->in->desc = &i_desc;
dbgp.serial->out->desc = &o_desc; dbgp.serial->out->desc = &o_desc;
if (gserial_setup(gadget, 1) < 0) { if (gserial_alloc_line(&tty_line)) {
stp = 3; stp = 3;
goto fail_3; goto fail_3;
} }
@ -377,7 +381,7 @@ static int dbgp_setup(struct usb_gadget *gadget,
#ifdef CONFIG_USB_G_DBGP_PRINTK #ifdef CONFIG_USB_G_DBGP_PRINTK
err = dbgp_enable_ep(); err = dbgp_enable_ep();
#else #else
err = gserial_connect(dbgp.serial, 0); err = gserial_connect(dbgp.serial, tty_line);
#endif #endif
if (err < 0) if (err < 0)
goto fail; goto fail;
@ -420,7 +424,7 @@ static void __exit dbgp_exit(void)
{ {
usb_gadget_unregister_driver(&dbgp_driver); usb_gadget_unregister_driver(&dbgp_driver);
#ifdef CONFIG_USB_G_DBGP_SERIAL #ifdef CONFIG_USB_G_DBGP_SERIAL
gserial_cleanup(); gserial_free_line(tty_line);
#endif #endif
} }

View File

@ -719,9 +719,6 @@ acm_unbind(struct usb_configuration *c, struct usb_function *f)
* *
* Returns zero on success, else negative errno. * Returns zero on success, else negative errno.
* *
* Caller must have called @gserial_setup() with enough ports to
* handle all the ones it binds. Caller is also responsible
* for calling @gserial_cleanup() before module unload.
*/ */
int acm_bind_config(struct usb_configuration *c, u8 port_num) int acm_bind_config(struct usb_configuration *c, u8 port_num)
{ {

View File

@ -406,10 +406,6 @@ static inline bool can_support_obex(struct usb_configuration *c)
* Context: single threaded during gadget setup * Context: single threaded during gadget setup
* *
* Returns zero on success, else negative errno. * Returns zero on success, else negative errno.
*
* Caller must have called @gserial_setup() with enough ports to
* handle all the ones it binds. Caller is also responsible
* for calling @gserial_cleanup() before module unload.
*/ */
int __init obex_bind_config(struct usb_configuration *c, u8 port_num) int __init obex_bind_config(struct usb_configuration *c, u8 port_num)
{ {

View File

@ -260,10 +260,6 @@ gser_unbind(struct usb_configuration *c, struct usb_function *f)
* Context: single threaded during gadget setup * Context: single threaded during gadget setup
* *
* Returns zero on success, else negative errno. * Returns zero on success, else negative errno.
*
* Caller must have called @gserial_setup() with enough ports to
* handle all the ones it binds. Caller is also responsible
* for calling @gserial_cleanup() before module unload.
*/ */
int __init gser_bind_config(struct usb_configuration *c, u8 port_num) int __init gser_bind_config(struct usb_configuration *c, u8 port_num)
{ {

View File

@ -136,6 +136,7 @@ static struct fsg_common fsg_common;
static u8 hostaddr[ETH_ALEN]; static u8 hostaddr[ETH_ALEN];
static unsigned char tty_line;
/********** RNDIS **********/ /********** RNDIS **********/
@ -154,7 +155,7 @@ static __init int rndis_do_config(struct usb_configuration *c)
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = acm_bind_config(c, 0); ret = acm_bind_config(c, tty_line);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -205,7 +206,7 @@ static __init int cdc_do_config(struct usb_configuration *c)
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = acm_bind_config(c, 0); ret = acm_bind_config(c, tty_line);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -242,7 +243,6 @@ static int cdc_config_register(struct usb_composite_dev *cdev)
/****************************** Gadget Bind ******************************/ /****************************** Gadget Bind ******************************/
static int __ref multi_bind(struct usb_composite_dev *cdev) static int __ref multi_bind(struct usb_composite_dev *cdev)
{ {
struct usb_gadget *gadget = cdev->gadget; struct usb_gadget *gadget = cdev->gadget;
@ -260,7 +260,7 @@ static int __ref multi_bind(struct usb_composite_dev *cdev)
return status; return status;
/* set up serial link layer */ /* set up serial link layer */
status = gserial_setup(cdev->gadget, 1); status = gserial_alloc_line(&tty_line);
if (status < 0) if (status < 0)
goto fail0; goto fail0;
@ -300,7 +300,7 @@ static int __ref multi_bind(struct usb_composite_dev *cdev)
fail2: fail2:
fsg_common_put(&fsg_common); fsg_common_put(&fsg_common);
fail1: fail1:
gserial_cleanup(); gserial_free_line(tty_line);
fail0: fail0:
gether_cleanup(); gether_cleanup();
return status; return status;
@ -308,7 +308,7 @@ fail0:
static int __exit multi_unbind(struct usb_composite_dev *cdev) static int __exit multi_unbind(struct usb_composite_dev *cdev)
{ {
gserial_cleanup(); gserial_free_line(tty_line);
gether_cleanup(); gether_cleanup();
return 0; return 0;
} }

View File

@ -100,6 +100,15 @@ MODULE_LICENSE("GPL");
static u8 hostaddr[ETH_ALEN]; static u8 hostaddr[ETH_ALEN];
enum {
TTY_PORT_OBEX0,
TTY_PORT_OBEX1,
TTY_PORT_ACM,
TTY_PORTS_MAX,
};
static unsigned char tty_lines[TTY_PORTS_MAX];
static int __init nokia_bind_config(struct usb_configuration *c) static int __init nokia_bind_config(struct usb_configuration *c)
{ {
int status = 0; int status = 0;
@ -108,15 +117,15 @@ static int __init nokia_bind_config(struct usb_configuration *c)
if (status) if (status)
printk(KERN_DEBUG "could not bind phonet config\n"); printk(KERN_DEBUG "could not bind phonet config\n");
status = obex_bind_config(c, 0); status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX0]);
if (status) if (status)
printk(KERN_DEBUG "could not bind obex config %d\n", 0); printk(KERN_DEBUG "could not bind obex config %d\n", 0);
status = obex_bind_config(c, 1); status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX1]);
if (status) if (status)
printk(KERN_DEBUG "could not bind obex config %d\n", 0); printk(KERN_DEBUG "could not bind obex config %d\n", 0);
status = acm_bind_config(c, 2); status = acm_bind_config(c, tty_lines[TTY_PORT_ACM]);
if (status) if (status)
printk(KERN_DEBUG "could not bind acm config\n"); printk(KERN_DEBUG "could not bind acm config\n");
@ -147,14 +156,17 @@ static int __init nokia_bind(struct usb_composite_dev *cdev)
{ {
struct usb_gadget *gadget = cdev->gadget; struct usb_gadget *gadget = cdev->gadget;
int status; int status;
int cur_line;
status = gphonet_setup(cdev->gadget); status = gphonet_setup(cdev->gadget);
if (status < 0) if (status < 0)
goto err_phonet; goto err_phonet;
status = gserial_setup(cdev->gadget, 3); for (cur_line = 0; cur_line < TTY_PORTS_MAX; cur_line++) {
if (status < 0) status = gserial_alloc_line(&tty_lines[cur_line]);
goto err_serial; if (status)
goto err_ether;
}
status = gether_setup(cdev->gadget, hostaddr); status = gether_setup(cdev->gadget, hostaddr);
if (status < 0) if (status < 0)
@ -191,8 +203,10 @@ static int __init nokia_bind(struct usb_composite_dev *cdev)
err_usb: err_usb:
gether_cleanup(); gether_cleanup();
err_ether: err_ether:
gserial_cleanup(); cur_line--;
err_serial: while (cur_line >= 0)
gserial_free_line(tty_lines[cur_line--]);
gphonet_cleanup(); gphonet_cleanup();
err_phonet: err_phonet:
return status; return status;
@ -200,8 +214,13 @@ err_phonet:
static int __exit nokia_unbind(struct usb_composite_dev *cdev) static int __exit nokia_unbind(struct usb_composite_dev *cdev)
{ {
int i;
gphonet_cleanup(); gphonet_cleanup();
gserial_cleanup();
for (i = 0; i < TTY_PORTS_MAX; i++)
gserial_free_line(tty_lines[i]);
gether_cleanup(); gether_cleanup();
return 0; return 0;

View File

@ -127,6 +127,7 @@ module_param(n_ports, uint, 0);
MODULE_PARM_DESC(n_ports, "number of ports to create, default=1"); MODULE_PARM_DESC(n_ports, "number of ports to create, default=1");
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static unsigned char tty_lines[MAX_U_SERIAL_PORTS];
static int __init serial_bind_acm_config(struct usb_configuration *c) static int __init serial_bind_acm_config(struct usb_configuration *c)
{ {
@ -134,7 +135,7 @@ static int __init serial_bind_acm_config(struct usb_configuration *c)
int status = 0; int status = 0;
for (i = 0; i < n_ports && status == 0; i++) for (i = 0; i < n_ports && status == 0; i++)
status = acm_bind_config(c, i); status = acm_bind_config(c, tty_lines[i]);
return status; return status;
} }
@ -144,7 +145,7 @@ static int __init serial_bind_obex_config(struct usb_configuration *c)
int status = 0; int status = 0;
for (i = 0; i < n_ports && status == 0; i++) for (i = 0; i < n_ports && status == 0; i++)
status = obex_bind_config(c, i); status = obex_bind_config(c, tty_lines[i]);
return status; return status;
} }
@ -154,7 +155,7 @@ static int __init serial_bind_gser_config(struct usb_configuration *c)
int status = 0; int status = 0;
for (i = 0; i < n_ports && status == 0; i++) for (i = 0; i < n_ports && status == 0; i++)
status = gser_bind_config(c, i); status = gser_bind_config(c, tty_lines[i]);
return status; return status;
} }
@ -165,13 +166,25 @@ static struct usb_configuration serial_config_driver = {
.bmAttributes = USB_CONFIG_ATT_SELFPOWER, .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
}; };
static int gs_unbind(struct usb_composite_dev *cdev)
{
int i;
for (i = 0; i < n_ports; i++)
gserial_free_line(tty_lines[i]);
return 0;
}
static int __init gs_bind(struct usb_composite_dev *cdev) static int __init gs_bind(struct usb_composite_dev *cdev)
{ {
int status; int status;
int cur_line;
status = gserial_setup(cdev->gadget, n_ports); for (cur_line = 0; cur_line < n_ports; cur_line++) {
if (status < 0) status = gserial_alloc_line(&tty_lines[cur_line]);
return status; if (status)
goto fail;
}
/* Allocate string descriptor numbers ... note that string /* Allocate string descriptor numbers ... note that string
* contents can be overridden by the composite_dev glue. * contents can be overridden by the composite_dev glue.
@ -209,7 +222,9 @@ static int __init gs_bind(struct usb_composite_dev *cdev)
return 0; return 0;
fail: fail:
gserial_cleanup(); cur_line--;
while (cur_line >= 0)
gserial_free_line(tty_lines[cur_line--]);
return status; return status;
} }
@ -219,6 +234,7 @@ static __refdata struct usb_composite_driver gserial_driver = {
.strings = dev_strings, .strings = dev_strings,
.max_speed = USB_SPEED_SUPER, .max_speed = USB_SPEED_SUPER,
.bind = gs_bind, .bind = gs_bind,
.unbind = gs_unbind,
}; };
static int __init init(void) static int __init init(void)
@ -254,6 +270,5 @@ module_init(init);
static void __exit cleanup(void) static void __exit cleanup(void)
{ {
usb_composite_unregister(&gserial_driver); usb_composite_unregister(&gserial_driver);
gserial_cleanup();
} }
module_exit(cleanup); module_exit(cleanup);

View File

@ -36,11 +36,12 @@
* "serial port" functionality through the USB gadget stack. Each such * "serial port" functionality through the USB gadget stack. Each such
* port is exposed through a /dev/ttyGS* node. * port is exposed through a /dev/ttyGS* node.
* *
* After initialization (gserial_setup), these TTY port devices stay * After this module has been loaded, the individual TTY port can be requested
* available until they are removed (gserial_cleanup). Each one may be * (gserial_alloc_line()) and it will stay available until they are removed
* connected to a USB function (gserial_connect), or disconnected (with * (gserial_free_line()). Each one may be connected to a USB function
* gserial_disconnect) when the USB host issues a config change event. * (gserial_connect), or disconnected (with gserial_disconnect) when the USB
* Data can only flow when the port is connected to the host. * 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. * A given TTY port can be made available in multiple configurations.
* For example, each one might expose a ttyGS0 node which provides a * 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 */ 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 { static struct portmaster {
struct mutex lock; /* protect open/close */ struct mutex lock; /* protect open/close */
struct gs_port *port; struct gs_port *port;
} ports[N_PORTS]; } ports[MAX_U_SERIAL_PORTS];
static unsigned n_ports;
#define GS_CLOSE_TIMEOUT 15 /* seconds */ #define GS_CLOSE_TIMEOUT 15 /* seconds */
@ -1033,10 +1031,19 @@ static int
gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
{ {
struct gs_port *port; 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); port = kzalloc(sizeof(struct gs_port), GFP_KERNEL);
if (port == NULL) if (port == NULL) {
return -ENOMEM; ret = -ENOMEM;
goto out;
}
tty_port_init(&port->port); tty_port_init(&port->port);
spin_lock_init(&port->port_lock); spin_lock_init(&port->port_lock);
@ -1052,115 +1059,11 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
port->port_line_coding = *coding; port->port_line_coding = *coding;
ports[port_num].port = port; ports[port_num].port = port;
out:
return 0; mutex_unlock(&ports[port_num].lock);
return ret;
} }
/**
* 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;
}
EXPORT_SYMBOL_GPL(gserial_setup);
static int gs_closed(struct gs_port *port) static int gs_closed(struct gs_port *port)
{ {
int cond; int cond;
@ -1171,56 +1074,77 @@ static int gs_closed(struct gs_port *port)
return cond; return cond;
} }
/** static void gserial_free_port(struct gs_port *port)
* gserial_cleanup - remove TTY-over-USB driver and devices {
* Context: may sleep tasklet_kill(&port->push);
* /* wait for old opens to finish */
* This is called to free all resources allocated by @gserial_setup(). wait_event(port->port.close_wait, gs_closed(port));
* Accordingly, it may need to wait until some open /dev/ files have WARN_ON(port->port_usb != NULL);
* closed. tty_port_destroy(&port->port);
* kfree(port);
* 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_free_line(unsigned char port_num)
*/
void gserial_cleanup(void)
{ {
unsigned i;
struct gs_port *port; 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; return;
/* 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);
/* 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);
} }
n_ports = 0; port = ports[port_num].port;
ports[port_num].port = NULL;
mutex_unlock(&ports[port_num].lock);
tty_unregister_driver(gs_tty_driver); gserial_free_port(port);
put_tty_driver(gs_tty_driver); tty_unregister_device(gs_tty_driver, port_num);
gs_tty_driver = NULL;
pr_debug("%s: cleaned up ttyGS* support\n", __func__);
} }
EXPORT_SYMBOL_GPL(gserial_cleanup); EXPORT_SYMBOL_GPL(gserial_free_line);
int gserial_alloc_line(unsigned char *line_num)
{
struct usb_cdc_line_coding coding;
struct device *tty_dev;
int ret;
int port_num;
coding.dwDTERate = cpu_to_le32(9600);
coding.bCharFormat = 8;
coding.bParityType = USB_CDC_NO_PARITY;
coding.bDataBits = USB_CDC_1_STOP_BITS;
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;
}
if (ret)
return ret;
/* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
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_alloc_line);
/** /**
* gserial_connect - notify TTY I/O glue that USB link is active * 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 * Caller needs to have set up the endpoints and USB function in @dev
* before calling this, as well as the appropriate (speed-specific) * before calling this, as well as the appropriate (speed-specific)
* endpoint descriptors, and also have set up the TTY driver by calling * endpoint descriptors, and also have allocate @port_num by calling
* @gserial_setup(). * @gserial_alloc_line().
* *
* Returns negative errno or zero. * Returns negative errno or zero.
* On success, ep->driver_data will be overwritten. * On success, ep->driver_data will be overwritten.
@ -1249,11 +1173,18 @@ int gserial_connect(struct gserial *gser, u8 port_num)
unsigned long flags; unsigned long flags;
int status; int status;
if (!gs_tty_driver || port_num >= n_ports) if (port_num >= MAX_U_SERIAL_PORTS)
return -ENXIO; return -ENXIO;
/* we "know" gserial_cleanup() hasn't been called */
port = ports[port_num].port; 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 */ /* activate the endpoints */
status = usb_ep_enable(gser->in); status = usb_ep_enable(gser->in);
@ -1357,4 +1288,63 @@ void gserial_disconnect(struct gserial *gser)
} }
EXPORT_SYMBOL_GPL(gserial_disconnect); 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"); MODULE_LICENSE("GPL");

View File

@ -15,6 +15,8 @@
#include <linux/usb/composite.h> #include <linux/usb/composite.h>
#include <linux/usb/cdc.h> #include <linux/usb/cdc.h>
#define MAX_U_SERIAL_PORTS 4
/* /*
* One non-multiplexed "serial" I/O port ... there can be several of these * One non-multiplexed "serial" I/O port ... there can be several of these
* on any given USB peripheral device, if it provides enough endpoints. * on any given USB peripheral device, if it provides enough endpoints.
@ -49,9 +51,9 @@ struct gserial {
struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags); struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
void gs_free_req(struct usb_ep *, struct usb_request *req); void gs_free_req(struct usb_ep *, struct usb_request *req);
/* port setup/teardown is handled by gadget driver */ /* management of individual TTY ports */
int gserial_setup(struct usb_gadget *g, unsigned n_ports); int gserial_alloc_line(unsigned char *port_line);
void gserial_cleanup(void); void gserial_free_line(unsigned char port_line);
/* connect/disconnect is handled by individual functions */ /* connect/disconnect is handled by individual functions */
int gserial_connect(struct gserial *, u8 port_num); int gserial_connect(struct gserial *, u8 port_num);