Commit 1365baf7 authored by Oliver Neukum's avatar Oliver Neukum Committed by Greg Kroah-Hartman

USB: autosuspend for cdc-acm

Here we go. This patch implements suspend/resume and autosuspend
for the CDC ACM driver.
Signed-off-by: default avatarOliver Neukum <oneukum@suse.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent eedffd12
...@@ -496,10 +496,19 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) ...@@ -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. */ otherwise it is scheduled, and with high data rates data can get lost. */
tty->low_latency = 1; 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++) { if (acm->used++) {
usb_autopm_put_interface(acm->control);
goto done; goto done;
} }
acm->ctrlurb->dev = acm->dev; acm->ctrlurb->dev = acm->dev;
if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) { if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
dbg("usb_submit_urb(ctrl irq) failed"); dbg("usb_submit_urb(ctrl irq) failed");
...@@ -526,14 +535,15 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) ...@@ -526,14 +535,15 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
done: done:
err_out: err_out:
mutex_unlock(&open_mutex); mutex_unlock(&acm->mutex);
return rv; return rv;
full_bailout: full_bailout:
usb_kill_urb(acm->ctrlurb); usb_kill_urb(acm->ctrlurb);
bail_out: bail_out:
usb_autopm_put_interface(acm->control);
acm->used--; acm->used--;
mutex_unlock(&open_mutex); mutex_unlock(&acm->mutex);
return -EIO; return -EIO;
} }
...@@ -570,6 +580,7 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) ...@@ -570,6 +580,7 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
usb_kill_urb(acm->writeurb); usb_kill_urb(acm->writeurb);
for (i = 0; i < nr; i++) for (i = 0; i < nr; i++)
usb_kill_urb(acm->ru[i].urb); usb_kill_urb(acm->ru[i].urb);
usb_autopm_put_interface(acm->control);
} else } else
acm_tty_unregister(acm); acm_tty_unregister(acm);
} }
...@@ -980,6 +991,7 @@ static int acm_probe (struct usb_interface *intf, ...@@ -980,6 +991,7 @@ static int acm_probe (struct usb_interface *intf,
spin_lock_init(&acm->throttle_lock); spin_lock_init(&acm->throttle_lock);
spin_lock_init(&acm->write_lock); spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock); spin_lock_init(&acm->read_lock);
mutex_init(&acm->mutex);
acm->write_ready = 1; acm->write_ready = 1;
acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
...@@ -1096,6 +1108,25 @@ static int acm_probe (struct usb_interface *intf, ...@@ -1096,6 +1108,25 @@ static int acm_probe (struct usb_interface *intf,
return -ENOMEM; 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) static void acm_disconnect(struct usb_interface *intf)
{ {
struct acm *acm = usb_get_intfdata(intf); struct acm *acm = usb_get_intfdata(intf);
...@@ -1123,19 +1154,7 @@ static void acm_disconnect(struct usb_interface *intf) ...@@ -1123,19 +1154,7 @@ static void acm_disconnect(struct usb_interface *intf)
usb_set_intfdata(acm->control, NULL); usb_set_intfdata(acm->control, NULL);
usb_set_intfdata(acm->data, NULL); usb_set_intfdata(acm->data, NULL);
tasklet_disable(&acm->urb_task); stop_data_traffic(acm);
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 */
acm_write_buffers_free(acm); acm_write_buffers_free(acm);
usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); 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) ...@@ -1156,6 +1175,46 @@ static void acm_disconnect(struct usb_interface *intf)
tty_hangup(acm->tty); 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. * USB driver structure.
*/ */
...@@ -1208,7 +1267,10 @@ static struct usb_driver acm_driver = { ...@@ -1208,7 +1267,10 @@ static struct usb_driver acm_driver = {
.name = "cdc_acm", .name = "cdc_acm",
.probe = acm_probe, .probe = acm_probe,
.disconnect = acm_disconnect, .disconnect = acm_disconnect,
.suspend = acm_suspend,
.resume = acm_resume,
.id_table = acm_ids, .id_table = acm_ids,
.supports_autosuspend = 1,
}; };
/* /*
......
...@@ -107,6 +107,7 @@ struct acm { ...@@ -107,6 +107,7 @@ struct acm {
int write_used; /* number of non-empty write buffers */ int write_used; /* number of non-empty write buffers */
int write_ready; /* write urb is not running */ int write_ready; /* write urb is not running */
spinlock_t write_lock; spinlock_t write_lock;
struct mutex mutex;
struct usb_cdc_line_coding line; /* bits, stop, parity */ struct usb_cdc_line_coding line; /* bits, stop, parity */
struct work_struct work; /* work queue entry for line discipline waking up */ struct work_struct work; /* work queue entry for line discipline waking up */
struct tasklet_struct urb_task; /* rx processing */ struct tasklet_struct urb_task; /* rx processing */
...@@ -120,6 +121,7 @@ struct acm { ...@@ -120,6 +121,7 @@ struct acm {
unsigned char throttle; /* throttled by tty layer */ unsigned char throttle; /* throttled by tty layer */
unsigned char clocal; /* termios CLOCAL */ unsigned char clocal; /* termios CLOCAL */
unsigned int ctrl_caps; /* control capabilities from the class specific header */ 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 #define CDC_DATA_INTERFACE_TYPE 0x0a
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment