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:
Родитель
188d636027
Коммит
11ea859d64
|
@ -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)
|
||||||
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)
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
/* 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)
|
||||||
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 @@ urbs:
|
||||||
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 @@ urbs:
|
||||||
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)
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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 @@ skip_normal_probe:
|
||||||
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 @@ alloc_fail:
|
||||||
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)
|
||||||
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)
|
||||||
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->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;
|
return 0;
|
||||||
/*
|
/*
|
||||||
we treat opened interfaces differently,
|
we treat opened interfaces differently,
|
||||||
|
@ -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 {
|
||||||
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 {
|
||||||
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
|
||||||
|
|
Загрузка…
Ссылка в новой задаче