USB: autosuspend for cdc-acm
authorOliver Neukum <oliver@neukum.org>
Fri, 12 Oct 2007 15:24:28 +0000 (17:24 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 1 Feb 2008 22:34:47 +0000 (14:34 -0800)
Here we go. This patch implements suspend/resume and autosuspend
for the CDC ACM driver.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/class/cdc-acm.c
drivers/usb/class/cdc-acm.h

index 912d97aaf9bf7b7ad52068dc850a8332aa4a76e5..b0f9873ce6b72f5f9e257edd16dfde71eeea9ae6 100644 (file)
@@ -496,10 +496,19 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
           otherwise it is scheduled, and with high data rates data can get lost. */
        tty->low_latency = 1;
 
+       if (usb_autopm_get_interface(acm->control)) {
+               mutex_unlock(&open_mutex);
+               return -EIO;
+       }
+
+       mutex_lock(&acm->mutex);
+       mutex_unlock(&open_mutex);
        if (acm->used++) {
+               usb_autopm_put_interface(acm->control);
                goto done;
         }
 
+
        acm->ctrlurb->dev = acm->dev;
        if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
                dbg("usb_submit_urb(ctrl irq) failed");
@@ -526,14 +535,15 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
 
 done:
 err_out:
-       mutex_unlock(&open_mutex);
+       mutex_unlock(&acm->mutex);
        return rv;
 
 full_bailout:
        usb_kill_urb(acm->ctrlurb);
 bail_out:
+       usb_autopm_put_interface(acm->control);
        acm->used--;
-       mutex_unlock(&open_mutex);
+       mutex_unlock(&acm->mutex);
        return -EIO;
 }
 
@@ -570,6 +580,7 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
                        usb_kill_urb(acm->writeurb);
                        for (i = 0; i < nr; i++)
                                usb_kill_urb(acm->ru[i].urb);
+                       usb_autopm_put_interface(acm->control);
                } else
                        acm_tty_unregister(acm);
        }
@@ -980,6 +991,7 @@ skip_normal_probe:
        spin_lock_init(&acm->throttle_lock);
        spin_lock_init(&acm->write_lock);
        spin_lock_init(&acm->read_lock);
+       mutex_init(&acm->mutex);
        acm->write_ready = 1;
        acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
 
@@ -1096,6 +1108,25 @@ alloc_fail:
        return -ENOMEM;
 }
 
+static void stop_data_traffic(struct acm *acm)
+{
+       int i;
+
+       tasklet_disable(&acm->urb_task);
+
+       usb_kill_urb(acm->ctrlurb);
+       usb_kill_urb(acm->writeurb);
+       for (i = 0; i < acm->rx_buflimit; i++)
+               usb_kill_urb(acm->ru[i].urb);
+
+       INIT_LIST_HEAD(&acm->filled_read_bufs);
+       INIT_LIST_HEAD(&acm->spare_read_bufs);
+
+       tasklet_enable(&acm->urb_task);
+
+       cancel_work_sync(&acm->work);
+}
+
 static void acm_disconnect(struct usb_interface *intf)
 {
        struct acm *acm = usb_get_intfdata(intf);
@@ -1123,19 +1154,7 @@ static void acm_disconnect(struct usb_interface *intf)
        usb_set_intfdata(acm->control, NULL);
        usb_set_intfdata(acm->data, NULL);
 
-       tasklet_disable(&acm->urb_task);
-
-       usb_kill_urb(acm->ctrlurb);
-       usb_kill_urb(acm->writeurb);
-       for (i = 0; i < acm->rx_buflimit; i++)
-               usb_kill_urb(acm->ru[i].urb);
-
-       INIT_LIST_HEAD(&acm->filled_read_bufs);
-       INIT_LIST_HEAD(&acm->spare_read_bufs);
-
-       tasklet_enable(&acm->urb_task);
-
-       flush_scheduled_work(); /* wait for acm_softint */
+       stop_data_traffic(acm);
 
        acm_write_buffers_free(acm);
        usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
@@ -1156,6 +1175,46 @@ static void acm_disconnect(struct usb_interface *intf)
                tty_hangup(acm->tty);
 }
 
+static int acm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+       struct acm *acm = usb_get_intfdata(intf);
+
+       if (acm->susp_count++)
+               return 0;
+       /*
+       we treat opened interfaces differently,
+       we must guard against open
+       */
+       mutex_lock(&acm->mutex);
+
+       if (acm->used)
+               stop_data_traffic(acm);
+
+       mutex_unlock(&acm->mutex);
+       return 0;
+}
+
+static int acm_resume(struct usb_interface *intf)
+{
+       struct acm *acm = usb_get_intfdata(intf);
+       int rv = 0;
+
+       if (--acm->susp_count)
+               return 0;
+
+       mutex_lock(&acm->mutex);
+       if (acm->used) {
+               rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
+               if (rv < 0)
+               goto err_out;
+
+               tasklet_schedule(&acm->urb_task);
+       }
+
+err_out:
+       mutex_unlock(&acm->mutex);
+       return rv;
+}
 /*
  * USB driver structure.
  */
@@ -1208,7 +1267,10 @@ static struct usb_driver acm_driver = {
        .name =         "cdc_acm",
        .probe =        acm_probe,
        .disconnect =   acm_disconnect,
+       .suspend =      acm_suspend,
+       .resume =       acm_resume,
        .id_table =     acm_ids,
+       .supports_autosuspend = 1,
 };
 
 /*
index 09f7765dbf8dc74ad223f42a3455d4dddb221c8d..8df6a57dcf9e4ca188b368f62ca2f0b4b86df4c6 100644 (file)
@@ -107,6 +107,7 @@ struct acm {
        int write_used;                                 /* number of non-empty write buffers */
        int write_ready;                                /* write urb is not running */
        spinlock_t write_lock;
+       struct mutex mutex;
        struct usb_cdc_line_coding line;                /* bits, stop, parity */
        struct work_struct work;                        /* work queue entry for line discipline waking up */
        struct tasklet_struct urb_task;                 /* rx processing */
@@ -120,6 +121,7 @@ struct acm {
        unsigned char throttle;                         /* throttled by tty layer */
        unsigned char clocal;                           /* termios CLOCAL */
        unsigned int ctrl_caps;                         /* control capabilities from the class specific header */
+       unsigned int susp_count;                        /* number of suspended interfaces */
 };
 
 #define CDC_DATA_INTERFACE_TYPE        0x0a