diff options
Diffstat (limited to 'drivers/usb/gadget/u_serial.c')
| -rw-r--r-- | drivers/usb/gadget/u_serial.c | 313 | 
1 files changed, 157 insertions, 156 deletions
diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index 598dcc1212f..588a9be18ef 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -26,6 +26,7 @@  #include <linux/tty_flip.h>  #include <linux/slab.h>  #include <linux/export.h> +#include <linux/module.h>  #include "u_serial.h" @@ -35,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 @@ -119,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 */ @@ -309,6 +308,7 @@ gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)  	return req;  } +EXPORT_SYMBOL_GPL(gs_alloc_req);  /*   * gs_free_req @@ -320,6 +320,7 @@ void gs_free_req(struct usb_ep *ep, struct usb_request *req)  	kfree(req->buf);  	usb_ep_free_request(ep, req);  } +EXPORT_SYMBOL_GPL(gs_free_req);  /*   * gs_send_packet @@ -1030,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); @@ -1049,109 +1059,9 @@ 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; - -	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;  }  static int gs_closed(struct gs_port *port) @@ -1164,55 +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* */ + +	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)); -	pr_debug("%s: cleaned up ttyGS* support\n", __func__); +		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 @@ -1229,8 +1161,8 @@ void gserial_cleanup(void)   *   * 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. @@ -1241,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); @@ -1292,7 +1231,7 @@ fail_out:  	gser->in->driver_data = NULL;  	return status;  } - +EXPORT_SYMBOL_GPL(gserial_connect);  /**   * gserial_disconnect - notify TTY I/O glue that USB link is inactive   * @gser: the function, on which gserial_connect() was called @@ -1347,3 +1286,65 @@ void gserial_disconnect(struct gserial *gser)  	spin_unlock_irqrestore(&port->port_lock, flags);  } +EXPORT_SYMBOL_GPL(gserial_disconnect); + +static 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");  |