mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 01:10:04 +00:00
USB: additional power savings for cdc-acm devices that support remote wakeup
this patch saves power for cdc-acm devices that support remote wakeup while the device is connected. - request needs_remote_wakeup when needed - delayed write while a device is autoresumed - the device is marked busy when appropriate Signed-off-by: Oliver Neukum <oneukum@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
188d636027
commit
11ea859d64
2 changed files with 126 additions and 21 deletions
|
@ -159,12 +159,34 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb)
|
|||
spin_lock_irqsave(&acm->write_lock, flags);
|
||||
acm->write_ready = 1;
|
||||
wb->use = 0;
|
||||
acm->transmitting--;
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Poke write.
|
||||
*
|
||||
* the caller is responsible for locking
|
||||
*/
|
||||
|
||||
static int acm_start_wb(struct acm *acm, struct acm_wb *wb)
|
||||
{
|
||||
int rc;
|
||||
|
||||
acm->transmitting++;
|
||||
|
||||
wb->urb->transfer_buffer = wb->buf;
|
||||
wb->urb->transfer_dma = wb->dmah;
|
||||
wb->urb->transfer_buffer_length = wb->len;
|
||||
wb->urb->dev = acm->dev;
|
||||
|
||||
if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) {
|
||||
dbg("usb_submit_urb(write bulk) failed: %d", rc);
|
||||
acm_write_done(acm, wb);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int acm_write_start(struct acm *acm, int wbn)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -182,26 +204,31 @@ static int acm_write_start(struct acm *acm, int wbn)
|
|||
return 0; /* A white lie */
|
||||
}
|
||||
|
||||
wb = &acm->wb[wbn];
|
||||
if(acm_wb_is_avail(acm) <= 1)
|
||||
acm->write_ready = 0;
|
||||
|
||||
dbg("%s susp_count: %d", __func__, acm->susp_count);
|
||||
if (acm->susp_count) {
|
||||
acm->old_ready = acm->write_ready;
|
||||
acm->delayed_wb = wb;
|
||||
acm->write_ready = 0;
|
||||
schedule_work(&acm->waker);
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
return 0; /* A white lie */
|
||||
}
|
||||
usb_mark_last_busy(acm->dev);
|
||||
|
||||
if (!acm_wb_is_used(acm, wbn)) {
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
wb = &acm->wb[wbn];
|
||||
|
||||
if(acm_wb_is_avail(acm) <= 1)
|
||||
acm->write_ready = 0;
|
||||
rc = acm_start_wb(acm, wb);
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
|
||||
wb->urb->transfer_buffer = wb->buf;
|
||||
wb->urb->transfer_dma = wb->dmah;
|
||||
wb->urb->transfer_buffer_length = wb->len;
|
||||
wb->urb->dev = acm->dev;
|
||||
|
||||
if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) {
|
||||
dbg("usb_submit_urb(write bulk) failed: %d", rc);
|
||||
acm_write_done(acm, wb);
|
||||
}
|
||||
return rc;
|
||||
|
||||
}
|
||||
/*
|
||||
* attributes exported through sysfs
|
||||
|
@ -304,6 +331,7 @@ static void acm_ctrl_irq(struct urb *urb)
|
|||
break;
|
||||
}
|
||||
exit:
|
||||
usb_mark_last_busy(acm->dev);
|
||||
retval = usb_submit_urb (urb, GFP_ATOMIC);
|
||||
if (retval)
|
||||
err ("%s - usb_submit_urb failed with result %d",
|
||||
|
@ -320,8 +348,11 @@ static void acm_read_bulk(struct urb *urb)
|
|||
|
||||
dbg("Entering acm_read_bulk with status %d", status);
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
if (!ACM_READY(acm)) {
|
||||
dev_dbg(&acm->data->dev, "Aborting, acm not ready");
|
||||
return;
|
||||
}
|
||||
usb_mark_last_busy(acm->dev);
|
||||
|
||||
if (status)
|
||||
dev_dbg(&acm->data->dev, "bulk rx status %d\n", status);
|
||||
|
@ -331,6 +362,7 @@ static void acm_read_bulk(struct urb *urb)
|
|||
|
||||
if (likely(status == 0)) {
|
||||
spin_lock(&acm->read_lock);
|
||||
acm->processing++;
|
||||
list_add_tail(&rcv->list, &acm->spare_read_urbs);
|
||||
list_add_tail(&buf->list, &acm->filled_read_bufs);
|
||||
spin_unlock(&acm->read_lock);
|
||||
|
@ -343,7 +375,8 @@ static void acm_read_bulk(struct urb *urb)
|
|||
/* nevertheless the tasklet must be kicked unconditionally
|
||||
so the queue cannot dry up */
|
||||
}
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
if (likely(!acm->susp_count))
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
}
|
||||
|
||||
static void acm_rx_tasklet(unsigned long _acm)
|
||||
|
@ -354,16 +387,23 @@ static void acm_rx_tasklet(unsigned long _acm)
|
|||
struct acm_ru *rcv;
|
||||
unsigned long flags;
|
||||
unsigned char throttled;
|
||||
|
||||
dbg("Entering acm_rx_tasklet");
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
{
|
||||
dbg("acm_rx_tasklet: ACM not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&acm->throttle_lock, flags);
|
||||
throttled = acm->throttle;
|
||||
spin_unlock_irqrestore(&acm->throttle_lock, flags);
|
||||
if (throttled)
|
||||
{
|
||||
dbg("acm_rx_tasklet: throttled");
|
||||
return;
|
||||
}
|
||||
|
||||
next_buffer:
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
|
@ -403,6 +443,7 @@ static void acm_rx_tasklet(unsigned long _acm)
|
|||
while (!list_empty(&acm->spare_read_bufs)) {
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
if (list_empty(&acm->spare_read_urbs)) {
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
@ -425,18 +466,23 @@ static void acm_rx_tasklet(unsigned long _acm)
|
|||
rcv->urb->transfer_dma = buf->dma;
|
||||
rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
|
||||
dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
|
||||
|
||||
/* This shouldn't kill the driver as unsuccessful URBs are returned to the
|
||||
free-urbs-pool and resubmited ASAP */
|
||||
if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
if (acm->susp_count || usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
|
||||
list_add(&buf->list, &acm->spare_read_bufs);
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
list_add(&rcv->list, &acm->spare_read_urbs);
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
return;
|
||||
} else {
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
|
||||
}
|
||||
}
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
}
|
||||
|
||||
/* data interface wrote those outgoing bytes */
|
||||
|
@ -463,6 +509,27 @@ static void acm_softint(struct work_struct *work)
|
|||
tty_wakeup(acm->tty);
|
||||
}
|
||||
|
||||
static void acm_waker(struct work_struct *waker)
|
||||
{
|
||||
struct acm *acm = container_of(waker, struct acm, waker);
|
||||
long flags;
|
||||
int rv;
|
||||
|
||||
rv = usb_autopm_get_interface(acm->control);
|
||||
if (rv < 0) {
|
||||
err("Autopm failure in %s", __func__);
|
||||
return;
|
||||
}
|
||||
if (acm->delayed_wb) {
|
||||
acm_start_wb(acm, acm->delayed_wb);
|
||||
acm->delayed_wb = NULL;
|
||||
}
|
||||
spin_lock_irqsave(&acm->write_lock, flags);
|
||||
acm->write_ready = acm->old_ready;
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
usb_autopm_put_interface(acm->control);
|
||||
}
|
||||
|
||||
/*
|
||||
* TTY handlers
|
||||
*/
|
||||
|
@ -492,6 +559,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
|
|||
|
||||
if (usb_autopm_get_interface(acm->control) < 0)
|
||||
goto early_bail;
|
||||
else
|
||||
acm->control->needs_remote_wakeup = 1;
|
||||
|
||||
mutex_lock(&acm->mutex);
|
||||
if (acm->used++) {
|
||||
|
@ -509,6 +578,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
|
|||
if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) &&
|
||||
(acm->ctrl_caps & USB_CDC_CAP_LINE))
|
||||
goto full_bailout;
|
||||
usb_autopm_put_interface(acm->control);
|
||||
|
||||
INIT_LIST_HEAD(&acm->spare_read_urbs);
|
||||
INIT_LIST_HEAD(&acm->spare_read_bufs);
|
||||
|
@ -570,12 +640,14 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
|
|||
mutex_lock(&open_mutex);
|
||||
if (!--acm->used) {
|
||||
if (acm->dev) {
|
||||
usb_autopm_get_interface(acm->control);
|
||||
acm_set_control(acm, acm->ctrlout = 0);
|
||||
usb_kill_urb(acm->ctrlurb);
|
||||
for (i = 0; i < ACM_NW; i++)
|
||||
usb_kill_urb(acm->wb[i].urb);
|
||||
for (i = 0; i < nr; i++)
|
||||
usb_kill_urb(acm->ru[i].urb);
|
||||
acm->control->needs_remote_wakeup = 0;
|
||||
usb_autopm_put_interface(acm->control);
|
||||
} else
|
||||
acm_tty_unregister(acm);
|
||||
|
@ -987,6 +1059,7 @@ static int acm_probe (struct usb_interface *intf,
|
|||
acm->urb_task.func = acm_rx_tasklet;
|
||||
acm->urb_task.data = (unsigned long) acm;
|
||||
INIT_WORK(&acm->work, acm_softint);
|
||||
INIT_WORK(&acm->waker, acm_waker);
|
||||
spin_lock_init(&acm->throttle_lock);
|
||||
spin_lock_init(&acm->write_lock);
|
||||
spin_lock_init(&acm->read_lock);
|
||||
|
@ -1116,6 +1189,7 @@ static int acm_probe (struct usb_interface *intf,
|
|||
static void stop_data_traffic(struct acm *acm)
|
||||
{
|
||||
int i;
|
||||
dbg("Entering stop_data_traffic");
|
||||
|
||||
tasklet_disable(&acm->urb_task);
|
||||
|
||||
|
@ -1128,6 +1202,7 @@ static void stop_data_traffic(struct acm *acm)
|
|||
tasklet_enable(&acm->urb_task);
|
||||
|
||||
cancel_work_sync(&acm->work);
|
||||
cancel_work_sync(&acm->waker);
|
||||
}
|
||||
|
||||
static void acm_disconnect(struct usb_interface *intf)
|
||||
|
@ -1181,8 +1256,27 @@ static void acm_disconnect(struct usb_interface *intf)
|
|||
static int acm_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
{
|
||||
struct acm *acm = usb_get_intfdata(intf);
|
||||
int cnt;
|
||||
|
||||
if (acm->susp_count++)
|
||||
if (acm->dev->auto_pm) {
|
||||
int b;
|
||||
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
spin_lock(&acm->write_lock);
|
||||
b = acm->processing + acm->transmitting;
|
||||
spin_unlock(&acm->write_lock);
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
if (b)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
spin_lock(&acm->write_lock);
|
||||
cnt = acm->susp_count++;
|
||||
spin_unlock(&acm->write_lock);
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
|
||||
if (cnt)
|
||||
return 0;
|
||||
/*
|
||||
we treat opened interfaces differently,
|
||||
|
@ -1201,15 +1295,21 @@ static int acm_resume(struct usb_interface *intf)
|
|||
{
|
||||
struct acm *acm = usb_get_intfdata(intf);
|
||||
int rv = 0;
|
||||
int cnt;
|
||||
|
||||
if (--acm->susp_count)
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
acm->susp_count -= 1;
|
||||
cnt = acm->susp_count;
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
|
||||
if (cnt)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&acm->mutex);
|
||||
if (acm->used) {
|
||||
rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
|
||||
if (rv < 0)
|
||||
goto err_out;
|
||||
goto err_out;
|
||||
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
}
|
||||
|
|
|
@ -107,10 +107,14 @@ struct acm {
|
|||
struct list_head filled_read_bufs;
|
||||
int write_used; /* number of non-empty write buffers */
|
||||
int write_ready; /* write urb is not running */
|
||||
int old_ready;
|
||||
int processing;
|
||||
int transmitting;
|
||||
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 work_struct waker;
|
||||
struct tasklet_struct urb_task; /* rx processing */
|
||||
spinlock_t throttle_lock; /* synchronize throtteling and read callback */
|
||||
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
|
||||
|
@ -123,6 +127,7 @@ struct acm {
|
|||
unsigned char clocal; /* termios CLOCAL */
|
||||
unsigned int ctrl_caps; /* control capabilities from the class specific header */
|
||||
unsigned int susp_count; /* number of suspended interfaces */
|
||||
struct acm_wb *delayed_wb; /* write queued for a device about to be woken */
|
||||
};
|
||||
|
||||
#define CDC_DATA_INTERFACE_TYPE 0x0a
|
||||
|
|
Loading…
Reference in a new issue