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 <oneukum@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
eedffd12e0
commit
1365baf724
2 changed files with 79 additions and 15 deletions
|
@ -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 @@ static int acm_probe (struct usb_interface *intf,
|
|||
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 @@ static int acm_probe (struct usb_interface *intf,
|
|||
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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue