Subject: [PATCH 002/002] USB: serial: sierra driver autosuspend support From: Elina Pasheva <epasheva@xxxxxxxxxxxxxxxxxx> This patch [PATCH 002/002] addresses Oliver Neukum's comments for the similar patch we submitted in the spring: [PATCH 002/002] dealing with autosuspend support: - Renamed module parameter "suspend_support" to "support_autopm" for clarity - Implemented anchor interface on the transmit path so that the Tx urbs are kept track of and are released during suspend - Added sierra_suspend(), sierra_resume() functions and support. - Moved sierra_device structure at the bottom - Added entries for suspend and resume interfaces to sierra_device structure. - Added new device attribute "suspend_status" which reflects the power state for each device served by the sierra driver. - Added provisions to enable debugging by using DEBUG definition - Version number set to 1.7.2 to match our repository. Signed-off-by: Elina Pasheva <epasheva@xxxxxxxxxxxxxxxxxx> --- drivers/usb/serial/sierra.c | 150 ++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 15 deletions(-) --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -16,8 +16,9 @@ Portions based on the option driver by Matthias Urlichs <smurf@xxxxxxxxxxxxxx> Whom based his on the Keyspan driver by Hugh Blemings <hugh@xxxxxxxxxxxx> */ - -#define DRIVER_VERSION "v.1.3.7" +/* Uncomment to log function calls */ +/* #define DEBUG */ +#define DRIVER_VERSION "v.1.7.2" #define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer" #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" @@ -44,6 +45,11 @@ static int debug; static int nmea; +static int support_autopm; + +/* sysfs attributes */ +static int sierra_create_sysfs_attrs(struct usb_serial_port *port); +/* Note: attribute created for each port; removed on disconnect */ /* Used in interface blacklisting */ struct sierra_iface_info { @@ -257,18 +263,13 @@ static struct usb_device_id id_table [] }; MODULE_DEVICE_TABLE(usb, id_table); -static struct usb_driver sierra_driver = { - .name = "sierra", - .probe = usb_serial_probe, - .disconnect = usb_serial_disconnect, - .id_table = id_table, - .no_dynamic_id = 1, -}; - struct sierra_port_private { spinlock_t lock; /* lock the structure */ int outstanding_urbs; /* number of out urbs in flight */ - + struct usb_anchor submitted; /* in case we need to retract our + * submissions */ + int suspend_status; /* indicates whether device power has been + * suspended */ /* Input endpoints and buffers for this port */ struct urb *in_urbs[N_IN_URB]; @@ -386,6 +387,28 @@ static void sierra_release_urb(struct ur } } +/* Sysfs Attributes */ + +static ssize_t show_suspend_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port; + struct sierra_port_private *portdata; + + port = to_usb_serial_port(dev); + portdata = usb_get_serial_port_data(port); + + return snprintf(buf, 4096, "%i\n", portdata->suspend_status); +} + +static DEVICE_ATTR(suspend_status, S_IWUSR | S_IRUGO, show_suspend_status, + NULL); + +static int sierra_create_sysfs_attrs(struct usb_serial_port *port) +{ + return device_create_file(&port->dev, &dev_attr_suspend_status); +} + static void sierra_outdat_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; @@ -467,21 +490,24 @@ static int sierra_write(struct tty_struc /* Handle the need to send a zero length packet */ urb->transfer_flags |= URB_ZERO_PACKET; + usb_anchor_urb(urb, &portdata->submitted); /* send it down the pipe */ retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) { dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed " "with status = %d\n", __func__, retval); - goto error; + goto error_anchor; } - /* we are done with this urb, so let the host driver - * really free it when it is finished with it */ + /* release our reference to this urb, the USB core will eventually + * free it entirely */ usb_free_urb(urb); return writesize; -error: + +error_anchor: + usb_unanchor_urb(urb); usb_free_urb(urb); error_no_urb: kfree(buffer); @@ -711,6 +737,7 @@ static void sierra_close(struct usb_seri int i; struct usb_serial *serial = port->serial; struct sierra_port_private *portdata; + int time; dev_dbg(&port->dev, "%s\n", __func__); portdata = usb_get_serial_port_data(port); @@ -731,6 +758,11 @@ static void sierra_close(struct usb_seri sierra_release_urb(portdata->in_urbs[i]); portdata->in_urbs[i] = NULL; } + + time = usb_wait_anchor_empty_timeout(&portdata->submitted, + 1000); + if (!time) + usb_kill_anchored_urbs(&portdata->submitted); } } @@ -819,8 +851,19 @@ static int sierra_startup(struct usb_ser return -ENOMEM; } spin_lock_init(&portdata->lock); + init_usb_anchor(&portdata->submitted); + portdata->suspend_status = 0; /* Set the port private data pointer */ usb_set_serial_port_data(port, portdata); + if (support_autopm == 0) { + /* When the device does not support suspend condition + * indicate the device is busy */ + usb_autopm_disable(serial->interface); + } else { + /* When the device does support autosuspend + * indicate the device is not busy */ + usb_autopm_enable(serial->interface); + } } return 0; @@ -841,11 +884,82 @@ static void sierra_disconnect(struct usb portdata = usb_get_serial_port_data(port); if (!portdata) continue; + portdata->suspend_status = 0; + device_remove_file(&port->dev, &dev_attr_suspend_status); + kfree(portdata); usb_set_serial_port_data(port, NULL); } } +int sierra_suspend(struct usb_serial *serial, pm_message_t message) +{ + int i; + struct usb_serial_port *port; + struct sierra_port_private *portdata; + unsigned long flags; + int time; + + dev_dbg(&serial->dev->dev, "%s\n", __func__); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!port) + continue; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + /* indicate suspended power mode */ + portdata->suspend_status = 1; + sierra_stop_rx_urbs(port); + /* release tx urbs */ + time = usb_wait_anchor_empty_timeout(&portdata->submitted, + 1000); + if (!time) { + usb_kill_anchored_urbs(&portdata->submitted); + spin_lock_irqsave(&portdata->lock, flags); + /* set outstanding tx urbs counter to 0 */ + portdata->outstanding_urbs = 0; + spin_unlock_irqrestore(&portdata->lock, flags); + } + } /* end for loop */ + return 0; +} + +int sierra_resume(struct usb_serial *serial) +{ + int i; + struct usb_serial_port *port; + struct sierra_port_private *portdata; + + dev_dbg(&serial->dev->dev, "%s\n", __func__); + + for (i = 0; i < serial->num_ports ; i++) { + port = serial->port[i]; + if (!port) + continue; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + sierra_submit_rx_urbs(port, GFP_NOIO); + /* indicate non-suspended power mode */ + portdata->suspend_status = 0; + } + return 0; +} + +static struct usb_driver sierra_driver = { + .name = "sierra", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .suspend = usb_serial_suspend, + .resume = usb_serial_resume, + .id_table = id_table, + + .no_dynamic_id = 1, + .supports_autosuspend = 1, +}; + static struct usb_serial_driver sierra_device = { .driver = { .owner = THIS_MODULE, @@ -866,7 +980,10 @@ static struct usb_serial_driver sierra_d .tiocmset = sierra_tiocmset, .attach = sierra_startup, .disconnect = sierra_disconnect, + .port_probe = sierra_create_sysfs_attrs, .read_int_callback = sierra_instat_callback, + .suspend = sierra_suspend, + .resume = sierra_resume, }; /* Functions used by new usb-serial code. */ @@ -912,3 +1029,6 @@ MODULE_PARM_DESC(nmea, "NMEA streaming") module_param(debug, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(debug, "Debug messages"); + +module_param(support_autopm, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(support_autopm, "Device auto suspend support"); -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html