From 1365baf7249bb2d05e774e7681237b8e86f5007a Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 12 Oct 2007 17:24:28 +0200 Subject: [PATCH] USB: autosuspend for cdc-acm Here we go. This patch implements suspend/resume and autosuspend for the CDC ACM driver. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 92 +++++++++++++++++++++++++++++++------ drivers/usb/class/cdc-acm.h | 2 + 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 912d97aaf9bf..b0f9873ce6b7 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -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, }; /* diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 09f7765dbf8d..8df6a57dcf9e 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -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