mdm6600: Resume modem synchronously
authorBenoit Goby <benoit@android.com>
Wed, 12 Jan 2011 23:38:24 +0000 (15:38 -0800)
committerBenoit Goby <benoit@android.com>
Sat, 15 Jan 2011 01:25:57 +0000 (17:25 -0800)
If runtime pm auto-resumes the modem while the modem is being
disconnected, runtime pm might use the modem power spinlock after free.

Auto-resumes the modem synchronously and make sure there are no pending
wakeup requests before disconnect returns.

Don't request an auto-resume if the modem is suspended or this may
cause usb devices to be resumed in the wrong order.

Change-Id: I38bf4830d30bb8b7619e093e0ba1b3861d240aec
Signed-off-by: Benoit Goby <benoit@android.com>
drivers/usb/serial/mdm6600.c

index caa8164f360a210237299d06a023ca5675b5332f..fe2d2530d62d24fd9fcd5333d8b21d9e701dc480 100644 (file)
@@ -98,6 +98,7 @@ struct mdm6600_port {
        int opened;
        int number;
        u16 tiocm_status;
+       struct work_struct wake_work;
 };
 
 static int mdm6600_wake_irq;
@@ -112,16 +113,36 @@ static void mdm6600_read_bulk_cb(struct urb *urb);
 static void mdm6600_write_bulk_cb(struct urb *urb);
 static void mdm6600_release(struct usb_serial *serial);
 
-static irqreturn_t mdm6600_irq_handler(int irq, void *ptr)
+static void mdm6600_wake_work(struct work_struct *work)
 {
-       struct mdm6600_port *modem = ptr;
+       struct mdm6600_port *modem = container_of(work, struct mdm6600_port,
+               wake_work);
+       struct usb_interface *intf = modem->serial->interface;
 
-       wake_lock_timeout(&modem->readlock, MODEM_WAKELOCK_TIME);
+       dbg("%s: port %d", __func__, modem->number);
+
+       device_lock(&intf->dev);
+
+       if (intf->dev.power.status >= DPM_OFF ||
+                       intf->dev.power.status == DPM_RESUMING) {
+               device_unlock(&intf->dev);
+               return;
+       }
 
        /* let usbcore auto-resume the modem */
-       if (usb_autopm_get_interface_async(modem->serial->interface) == 0)
+       if (usb_autopm_get_interface(intf) == 0)
                /* set usage count back to 0 */
-               usb_autopm_put_interface_no_suspend(modem->serial->interface);
+               usb_autopm_put_interface_no_suspend(intf);
+
+       device_unlock(&intf->dev);
+}
+
+static irqreturn_t mdm6600_irq_handler(int irq, void *ptr)
+{
+       struct mdm6600_port *modem = ptr;
+
+       wake_lock_timeout(&modem->readlock, MODEM_WAKELOCK_TIME);
+       queue_work(system_nrt_wq, &modem->wake_work);
 
        return IRQ_HANDLED;
 }
@@ -242,6 +263,7 @@ static int mdm6600_attach(struct usb_serial *serial)
        serial->dev->parent->autosuspend_delay = 0;
 
        if (modem->number == MODEM_INTERFACE_NUM) {
+               INIT_WORK(&modem->wake_work, mdm6600_wake_work);
                status = request_irq(mdm6600_wake_irq, mdm6600_irq_handler,
                                IRQ_TYPE_EDGE_FALLING, "usb_wake_host", modem);
                if (status) {
@@ -299,6 +321,7 @@ static void mdm6600_disconnect(struct usb_serial *serial)
        if (modem->number == MODEM_INTERFACE_NUM) {
                disable_irq_wake(mdm6600_wake_irq);
                free_irq(mdm6600_wake_irq, modem);
+               cancel_work_sync(&modem->wake_work);
        }
 
        mdm6600_kill_urbs(modem);