Commit 11ea859d authored by Oliver Neukum's avatar Oliver Neukum Committed by Greg Kroah-Hartman

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: default avatarOliver Neukum <oneukum@suse.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 188d6360
...@@ -159,12 +159,34 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb) ...@@ -159,12 +159,34 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb)
spin_lock_irqsave(&acm->write_lock, flags); spin_lock_irqsave(&acm->write_lock, flags);
acm->write_ready = 1; acm->write_ready = 1;
wb->use = 0; wb->use = 0;
acm->transmitting--;
spin_unlock_irqrestore(&acm->write_lock, flags); spin_unlock_irqrestore(&acm->write_lock, flags);
} }
/* /*
* Poke write. * 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) static int acm_write_start(struct acm *acm, int wbn)
{ {
unsigned long flags; unsigned long flags;
...@@ -182,26 +204,31 @@ static int acm_write_start(struct acm *acm, int wbn) ...@@ -182,26 +204,31 @@ static int acm_write_start(struct acm *acm, int wbn)
return 0; /* A white lie */ 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)) { if (!acm_wb_is_used(acm, wbn)) {
spin_unlock_irqrestore(&acm->write_lock, flags); spin_unlock_irqrestore(&acm->write_lock, flags);
return 0; return 0;
} }
wb = &acm->wb[wbn];
if(acm_wb_is_avail(acm) <= 1) rc = acm_start_wb(acm, wb);
acm->write_ready = 0;
spin_unlock_irqrestore(&acm->write_lock, flags); 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; return rc;
} }
/* /*
* attributes exported through sysfs * attributes exported through sysfs
...@@ -304,6 +331,7 @@ static void acm_ctrl_irq(struct urb *urb) ...@@ -304,6 +331,7 @@ static void acm_ctrl_irq(struct urb *urb)
break; break;
} }
exit: exit:
usb_mark_last_busy(acm->dev);
retval = usb_submit_urb (urb, GFP_ATOMIC); retval = usb_submit_urb (urb, GFP_ATOMIC);
if (retval) if (retval)
err ("%s - usb_submit_urb failed with result %d", err ("%s - usb_submit_urb failed with result %d",
...@@ -320,8 +348,11 @@ static void acm_read_bulk(struct urb *urb) ...@@ -320,8 +348,11 @@ static void acm_read_bulk(struct urb *urb)
dbg("Entering acm_read_bulk with status %d", status); 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; return;
}
usb_mark_last_busy(acm->dev);
if (status) if (status)
dev_dbg(&acm->data->dev, "bulk rx status %d\n", status); dev_dbg(&acm->data->dev, "bulk rx status %d\n", status);
...@@ -331,6 +362,7 @@ static void acm_read_bulk(struct urb *urb) ...@@ -331,6 +362,7 @@ static void acm_read_bulk(struct urb *urb)
if (likely(status == 0)) { if (likely(status == 0)) {
spin_lock(&acm->read_lock); spin_lock(&acm->read_lock);
acm->processing++;
list_add_tail(&rcv->list, &acm->spare_read_urbs); list_add_tail(&rcv->list, &acm->spare_read_urbs);
list_add_tail(&buf->list, &acm->filled_read_bufs); list_add_tail(&buf->list, &acm->filled_read_bufs);
spin_unlock(&acm->read_lock); spin_unlock(&acm->read_lock);
...@@ -343,7 +375,8 @@ static void acm_read_bulk(struct urb *urb) ...@@ -343,7 +375,8 @@ static void acm_read_bulk(struct urb *urb)
/* nevertheless the tasklet must be kicked unconditionally /* nevertheless the tasklet must be kicked unconditionally
so the queue cannot dry up */ 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) static void acm_rx_tasklet(unsigned long _acm)
...@@ -354,16 +387,23 @@ static void acm_rx_tasklet(unsigned long _acm) ...@@ -354,16 +387,23 @@ static void acm_rx_tasklet(unsigned long _acm)
struct acm_ru *rcv; struct acm_ru *rcv;
unsigned long flags; unsigned long flags;
unsigned char throttled; unsigned char throttled;
dbg("Entering acm_rx_tasklet"); dbg("Entering acm_rx_tasklet");
if (!ACM_READY(acm)) if (!ACM_READY(acm))
{
dbg("acm_rx_tasklet: ACM not ready");
return; return;
}
spin_lock_irqsave(&acm->throttle_lock, flags); spin_lock_irqsave(&acm->throttle_lock, flags);
throttled = acm->throttle; throttled = acm->throttle;
spin_unlock_irqrestore(&acm->throttle_lock, flags); spin_unlock_irqrestore(&acm->throttle_lock, flags);
if (throttled) if (throttled)
{
dbg("acm_rx_tasklet: throttled");
return; return;
}
next_buffer: next_buffer:
spin_lock_irqsave(&acm->read_lock, flags); spin_lock_irqsave(&acm->read_lock, flags);
...@@ -403,6 +443,7 @@ static void acm_rx_tasklet(unsigned long _acm) ...@@ -403,6 +443,7 @@ static void acm_rx_tasklet(unsigned long _acm)
while (!list_empty(&acm->spare_read_bufs)) { while (!list_empty(&acm->spare_read_bufs)) {
spin_lock_irqsave(&acm->read_lock, flags); spin_lock_irqsave(&acm->read_lock, flags);
if (list_empty(&acm->spare_read_urbs)) { if (list_empty(&acm->spare_read_urbs)) {
acm->processing = 0;
spin_unlock_irqrestore(&acm->read_lock, flags); spin_unlock_irqrestore(&acm->read_lock, flags);
return; return;
} }
...@@ -425,18 +466,23 @@ static void acm_rx_tasklet(unsigned long _acm) ...@@ -425,18 +466,23 @@ static void acm_rx_tasklet(unsigned long _acm)
rcv->urb->transfer_dma = buf->dma; rcv->urb->transfer_dma = buf->dma;
rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 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 /* This shouldn't kill the driver as unsuccessful URBs are returned to the
free-urbs-pool and resubmited ASAP */ 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); list_add(&buf->list, &acm->spare_read_bufs);
spin_lock_irqsave(&acm->read_lock, flags);
list_add(&rcv->list, &acm->spare_read_urbs); list_add(&rcv->list, &acm->spare_read_urbs);
acm->processing = 0;
spin_unlock_irqrestore(&acm->read_lock, flags); spin_unlock_irqrestore(&acm->read_lock, flags);
return; 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 */ /* data interface wrote those outgoing bytes */
...@@ -463,6 +509,27 @@ static void acm_softint(struct work_struct *work) ...@@ -463,6 +509,27 @@ static void acm_softint(struct work_struct *work)
tty_wakeup(acm->tty); 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 * TTY handlers
*/ */
...@@ -492,6 +559,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) ...@@ -492,6 +559,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
if (usb_autopm_get_interface(acm->control) < 0) if (usb_autopm_get_interface(acm->control) < 0)
goto early_bail; goto early_bail;
else
acm->control->needs_remote_wakeup = 1;
mutex_lock(&acm->mutex); mutex_lock(&acm->mutex);
if (acm->used++) { if (acm->used++) {
...@@ -509,6 +578,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) ...@@ -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) && if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) &&
(acm->ctrl_caps & USB_CDC_CAP_LINE)) (acm->ctrl_caps & USB_CDC_CAP_LINE))
goto full_bailout; goto full_bailout;
usb_autopm_put_interface(acm->control);
INIT_LIST_HEAD(&acm->spare_read_urbs); INIT_LIST_HEAD(&acm->spare_read_urbs);
INIT_LIST_HEAD(&acm->spare_read_bufs); INIT_LIST_HEAD(&acm->spare_read_bufs);
...@@ -570,12 +640,14 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) ...@@ -570,12 +640,14 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
mutex_lock(&open_mutex); mutex_lock(&open_mutex);
if (!--acm->used) { if (!--acm->used) {
if (acm->dev) { if (acm->dev) {
usb_autopm_get_interface(acm->control);
acm_set_control(acm, acm->ctrlout = 0); acm_set_control(acm, acm->ctrlout = 0);
usb_kill_urb(acm->ctrlurb); usb_kill_urb(acm->ctrlurb);
for (i = 0; i < ACM_NW; i++) for (i = 0; i < ACM_NW; i++)
usb_kill_urb(acm->wb[i].urb); usb_kill_urb(acm->wb[i].urb);
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);
acm->control->needs_remote_wakeup = 0;
usb_autopm_put_interface(acm->control); usb_autopm_put_interface(acm->control);
} else } else
acm_tty_unregister(acm); acm_tty_unregister(acm);
...@@ -987,6 +1059,7 @@ static int acm_probe (struct usb_interface *intf, ...@@ -987,6 +1059,7 @@ static int acm_probe (struct usb_interface *intf,
acm->urb_task.func = acm_rx_tasklet; acm->urb_task.func = acm_rx_tasklet;
acm->urb_task.data = (unsigned long) acm; acm->urb_task.data = (unsigned long) acm;
INIT_WORK(&acm->work, acm_softint); INIT_WORK(&acm->work, acm_softint);
INIT_WORK(&acm->waker, acm_waker);
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);
...@@ -1116,6 +1189,7 @@ static int acm_probe (struct usb_interface *intf, ...@@ -1116,6 +1189,7 @@ static int acm_probe (struct usb_interface *intf,
static void stop_data_traffic(struct acm *acm) static void stop_data_traffic(struct acm *acm)
{ {
int i; int i;
dbg("Entering stop_data_traffic");
tasklet_disable(&acm->urb_task); tasklet_disable(&acm->urb_task);
...@@ -1128,6 +1202,7 @@ static void stop_data_traffic(struct acm *acm) ...@@ -1128,6 +1202,7 @@ static void stop_data_traffic(struct acm *acm)
tasklet_enable(&acm->urb_task); tasklet_enable(&acm->urb_task);
cancel_work_sync(&acm->work); cancel_work_sync(&acm->work);
cancel_work_sync(&acm->waker);
} }
static void acm_disconnect(struct usb_interface *intf) static void acm_disconnect(struct usb_interface *intf)
...@@ -1181,8 +1256,27 @@ 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) static int acm_suspend(struct usb_interface *intf, pm_message_t message)
{ {
struct acm *acm = usb_get_intfdata(intf); struct acm *acm = usb_get_intfdata(intf);
int cnt;
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 (acm->susp_count++) if (cnt)
return 0; return 0;
/* /*
we treat opened interfaces differently, we treat opened interfaces differently,
...@@ -1201,15 +1295,21 @@ static int acm_resume(struct usb_interface *intf) ...@@ -1201,15 +1295,21 @@ static int acm_resume(struct usb_interface *intf)
{ {
struct acm *acm = usb_get_intfdata(intf); struct acm *acm = usb_get_intfdata(intf);
int rv = 0; 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; return 0;
mutex_lock(&acm->mutex); mutex_lock(&acm->mutex);
if (acm->used) { if (acm->used) {
rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
if (rv < 0) if (rv < 0)
goto err_out; goto err_out;
tasklet_schedule(&acm->urb_task); tasklet_schedule(&acm->urb_task);
} }
......
...@@ -107,10 +107,14 @@ struct acm { ...@@ -107,10 +107,14 @@ struct acm {
struct list_head filled_read_bufs; struct list_head filled_read_bufs;
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 */
int old_ready;
int processing;
int transmitting;
spinlock_t write_lock; spinlock_t write_lock;
struct mutex mutex; 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 work_struct waker;
struct tasklet_struct urb_task; /* rx processing */ struct tasklet_struct urb_task; /* rx processing */
spinlock_t throttle_lock; /* synchronize throtteling and read callback */ spinlock_t throttle_lock; /* synchronize throtteling and read callback */
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
...@@ -123,6 +127,7 @@ struct acm { ...@@ -123,6 +127,7 @@ struct acm {
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 */ 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 #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