USB: serial: full autosuspend support for the option driver
authorOliver Neukum <oliver@neukum.org>
Wed, 1 Jul 2009 14:00:32 +0000 (16:00 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:25 +0000 (06:46 -0700)
this adds autosupport usable even in an always online mode.

- enables remote wakeup on open
- autoresume for sending
- timeout based autosuspend if nothing is sent or recieved
- autosuspend without remote wakeup support on open/close

Signed-off-by: Oliver Neukum <oliver@neukum.org>
Tested-off-by: Zhao Ming <zhao.ming9@zte.com.cn>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/option.c

index 67862d7..f66e398 100644 (file)
@@ -596,6 +596,7 @@ static struct usb_driver option_driver = {
 #ifdef CONFIG_PM
        .suspend    = usb_serial_suspend,
        .resume     = usb_serial_resume,
+       .supports_autosuspend = 1,
 #endif
        .id_table   = option_ids,
        .no_dynamic_id =        1,
@@ -643,6 +644,12 @@ static int debug;
 #define IN_BUFLEN 4096
 #define OUT_BUFLEN 4096
 
+struct option_intf_private {
+       spinlock_t susp_lock;
+       unsigned int suspended:1;
+       int in_flight;
+};
+
 struct option_port_private {
        /* Input endpoints and buffer for this port */
        struct urb *in_urbs[N_IN_URB];
@@ -651,6 +658,8 @@ struct option_port_private {
        struct urb *out_urbs[N_OUT_URB];
        u8 *out_buffer[N_OUT_URB];
        unsigned long out_busy;         /* Bit vector of URBs in use */
+       int opened;
+       struct usb_anchor delayed;
 
        /* Settings for the port */
        int rts_state;  /* Handshaking pins (outputs) */
@@ -697,12 +706,17 @@ module_exit(option_exit);
 static int option_probe(struct usb_serial *serial,
                        const struct usb_device_id *id)
 {
+       struct option_intf_private *data;
        /* D-Link DWM 652 still exposes CD-Rom emulation interface in modem mode */
        if (serial->dev->descriptor.idVendor == DLINK_VENDOR_ID &&
                serial->dev->descriptor.idProduct == DLINK_PRODUCT_DWM_652 &&
                serial->interface->cur_altsetting->desc.bInterfaceClass == 0x8)
                return -ENODEV;
 
+       data = serial->private = kzalloc(sizeof(struct option_intf_private), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+       spin_lock_init(&data->susp_lock);
        return 0;
 }
 
@@ -759,12 +773,15 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
                        const unsigned char *buf, int count)
 {
        struct option_port_private *portdata;
+       struct option_intf_private *intfdata;
        int i;
        int left, todo;
        struct urb *this_urb = NULL; /* spurious */
        int err;
+       unsigned long flags;
 
        portdata = usb_get_serial_port_data(port);
+       intfdata = port->serial->private;
 
        dbg("%s: write (%d chars)", __func__, count);
 
@@ -786,17 +803,33 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
                dbg("%s: endpoint %d buf %d", __func__,
                        usb_pipeendpoint(this_urb->pipe), i);
 
+               err = usb_autopm_get_interface_async(port->serial->interface);
+               if (err < 0)
+                       break;
+
                /* send the data */
                memcpy(this_urb->transfer_buffer, buf, todo);
                this_urb->transfer_buffer_length = todo;
 
-               err = usb_submit_urb(this_urb, GFP_ATOMIC);
-               if (err) {
-                       dbg("usb_submit_urb %p (write bulk) failed "
-                               "(%d)", this_urb, err);
-                       clear_bit(i, &portdata->out_busy);
-                       continue;
+               spin_lock_irqsave(&intfdata->susp_lock, flags);
+               if (intfdata->suspended) {
+                       usb_anchor_urb(this_urb, &portdata->delayed);
+                       spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+               } else {
+                       intfdata->in_flight++;
+                       spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+                       err = usb_submit_urb(this_urb, GFP_ATOMIC);
+                       if (err) {
+                               dbg("usb_submit_urb %p (write bulk) failed "
+                                       "(%d)", this_urb, err);
+                               clear_bit(i, &portdata->out_busy);
+                               spin_lock_irqsave(&intfdata->susp_lock, flags);
+                               intfdata->in_flight--;
+                               spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+                               continue;
+                       }
                }
+
                portdata->tx_start_time[i] = jiffies;
                buf += todo;
                left -= todo;
@@ -840,7 +873,10 @@ static void option_indat_callback(struct urb *urb)
                        if (err)
                                printk(KERN_ERR "%s: resubmit read urb failed. "
                                        "(%d)", __func__, err);
+                       else
+                               usb_mark_last_busy(port->serial->dev);
                }
+
        }
        return;
 }
@@ -849,15 +885,21 @@ static void option_outdat_callback(struct urb *urb)
 {
        struct usb_serial_port *port;
        struct option_port_private *portdata;
+       struct option_intf_private *intfdata;
        int i;
 
        dbg("%s", __func__);
 
        port =  urb->context;
+       intfdata = port->serial->private;
 
        usb_serial_port_softint(port);
-
+       usb_autopm_put_interface_async(port->serial->interface);
        portdata = usb_get_serial_port_data(port);
+       spin_lock(&intfdata->susp_lock);
+       intfdata->in_flight--;
+       spin_unlock(&intfdata->susp_lock);
+
        for (i = 0; i < N_OUT_URB; ++i) {
                if (portdata->out_urbs[i] == urb) {
                        smp_mb__before_clear_bit();
@@ -967,10 +1009,13 @@ static int option_chars_in_buffer(struct tty_struct *tty)
 static int option_open(struct tty_struct *tty, struct usb_serial_port *port)
 {
        struct option_port_private *portdata;
+       struct option_intf_private *intfdata;
+       struct usb_serial *serial = port->serial;
        int i, err;
        struct urb *urb;
 
        portdata = usb_get_serial_port_data(port);
+       intfdata = serial->private;
 
        dbg("%s", __func__);
 
@@ -989,6 +1034,12 @@ static int option_open(struct tty_struct *tty, struct usb_serial_port *port)
 
        option_send_setup(port);
 
+       serial->interface->needs_remote_wakeup = 1;
+       spin_lock_irq(&intfdata->susp_lock);
+       portdata->opened = 1;
+       spin_unlock_irq(&intfdata->susp_lock);
+       usb_autopm_put_interface(serial->interface);
+
        return 0;
 }
 
@@ -1013,16 +1064,23 @@ static void option_close(struct usb_serial_port *port)
        int i;
        struct usb_serial *serial = port->serial;
        struct option_port_private *portdata;
+       struct option_intf_private *intfdata = port->serial->private;
 
        dbg("%s", __func__);
        portdata = usb_get_serial_port_data(port);
 
        if (serial->dev) {
                /* Stop reading/writing urbs */
+               spin_lock_irq(&intfdata->susp_lock);
+               portdata->opened = 0;
+               spin_unlock_irq(&intfdata->susp_lock);
+
                for (i = 0; i < N_IN_URB; i++)
                        usb_kill_urb(portdata->in_urbs[i]);
                for (i = 0; i < N_OUT_URB; i++)
                        usb_kill_urb(portdata->out_urbs[i]);
+               usb_autopm_get_interface(serial->interface);
+               serial->interface->needs_remote_wakeup = 0;
        }
 }
 
@@ -1127,6 +1185,7 @@ static int option_startup(struct usb_serial *serial)
                                        __func__, i);
                        return 1;
                }
+               init_usb_anchor(&portdata->delayed);
 
                for (j = 0; j < N_IN_URB; j++) {
                        buffer = (u8 *)__get_free_page(GFP_KERNEL);
@@ -1229,18 +1288,52 @@ static void option_release(struct usb_serial *serial)
 #ifdef CONFIG_PM
 static int option_suspend(struct usb_serial *serial, pm_message_t message)
 {
+       struct option_intf_private *intfdata = serial->private;
+       int b;
+
        dbg("%s entered", __func__);
+
+       if (serial->dev->auto_pm) {
+               spin_lock_irq(&intfdata->susp_lock);
+               b = intfdata->in_flight;
+               spin_unlock_irq(&intfdata->susp_lock);
+
+               if (b)
+                       return -EBUSY;
+       }
+
+       spin_lock_irq(&intfdata->susp_lock);
+       intfdata->suspended = 1;
+       spin_unlock_irq(&intfdata->susp_lock);
        stop_read_write_urbs(serial);
 
        return 0;
 }
 
+static void play_delayed(struct usb_serial_port *port)
+{
+       struct option_intf_private *data;
+       struct option_port_private *portdata;
+       struct urb *urb;
+       int err;
+
+       portdata = usb_get_serial_port_data(port);
+       data = port->serial->private;
+       while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+               err = usb_submit_urb(urb, GFP_ATOMIC);
+               if (!err)
+                       data->in_flight++;
+       }
+}
+
 static int option_resume(struct usb_serial *serial)
 {
-       int err, i, j;
+       int i, j;
        struct usb_serial_port *port;
-       struct urb *urb;
+       struct option_intf_private *intfdata = serial->private;
        struct option_port_private *portdata;
+       struct urb *urb;
+       int err = 0;
 
        dbg("%s entered", __func__);
        /* get the interrupt URBs resubmitted unconditionally */
@@ -1255,7 +1348,7 @@ static int option_resume(struct usb_serial *serial)
                if (err < 0) {
                        err("%s: Error %d for interrupt URB of port%d",
                                 __func__, err, i);
-                       return err;
+                       goto err_out;
                }
        }
 
@@ -1263,27 +1356,32 @@ static int option_resume(struct usb_serial *serial)
                /* walk all ports */
                port = serial->port[i];
                portdata = usb_get_serial_port_data(port);
-               mutex_lock(&port->mutex);
 
                /* skip closed ports */
-               if (!port->port.count) {
-                       mutex_unlock(&port->mutex);
+               spin_lock_irq(&intfdata->susp_lock);
+               if (!portdata->opened) {
+                       spin_unlock_irq(&intfdata->susp_lock);
                        continue;
                }
 
                for (j = 0; j < N_IN_URB; j++) {
                        urb = portdata->in_urbs[j];
-                       err = usb_submit_urb(urb, GFP_NOIO);
+                       err = usb_submit_urb(urb, GFP_ATOMIC);
                        if (err < 0) {
-                               mutex_unlock(&port->mutex);
                                err("%s: Error %d for bulk URB %d",
                                         __func__, err, i);
-                               return err;
+                               spin_unlock_irq(&intfdata->susp_lock);
+                               goto err_out;
                        }
                }
-               mutex_unlock(&port->mutex);
+               play_delayed(port);
+               spin_unlock_irq(&intfdata->susp_lock);
        }
-       return 0;
+       spin_lock_irq(&intfdata->susp_lock);
+       intfdata->suspended = 0;
+       spin_unlock_irq(&intfdata->susp_lock);
+err_out:
+       return err;
 }
 #endif