Commit 11ea859d64b69a747d6b060b9ed1520eab1161fe
Committed by
Greg Kroah-Hartman
1 parent
188d636027
Exists in
master
and in
7 other branches
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>
Showing 2 changed files with 126 additions and 21 deletions Side-by-side Diff
drivers/usb/class/cdc-acm.c
... | ... | @@ -159,12 +159,34 @@ |
159 | 159 | spin_lock_irqsave(&acm->write_lock, flags); |
160 | 160 | acm->write_ready = 1; |
161 | 161 | wb->use = 0; |
162 | + acm->transmitting--; | |
162 | 163 | spin_unlock_irqrestore(&acm->write_lock, flags); |
163 | 164 | } |
164 | 165 | |
165 | 166 | /* |
166 | 167 | * Poke write. |
168 | + * | |
169 | + * the caller is responsible for locking | |
167 | 170 | */ |
171 | + | |
172 | +static int acm_start_wb(struct acm *acm, struct acm_wb *wb) | |
173 | +{ | |
174 | + int rc; | |
175 | + | |
176 | + acm->transmitting++; | |
177 | + | |
178 | + wb->urb->transfer_buffer = wb->buf; | |
179 | + wb->urb->transfer_dma = wb->dmah; | |
180 | + wb->urb->transfer_buffer_length = wb->len; | |
181 | + wb->urb->dev = acm->dev; | |
182 | + | |
183 | + if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { | |
184 | + dbg("usb_submit_urb(write bulk) failed: %d", rc); | |
185 | + acm_write_done(acm, wb); | |
186 | + } | |
187 | + return rc; | |
188 | +} | |
189 | + | |
168 | 190 | static int acm_write_start(struct acm *acm, int wbn) |
169 | 191 | { |
170 | 192 | unsigned long flags; |
171 | 193 | |
172 | 194 | |
173 | 195 | |
174 | 196 | |
... | ... | @@ -182,26 +204,31 @@ |
182 | 204 | return 0; /* A white lie */ |
183 | 205 | } |
184 | 206 | |
207 | + wb = &acm->wb[wbn]; | |
208 | + if(acm_wb_is_avail(acm) <= 1) | |
209 | + acm->write_ready = 0; | |
210 | + | |
211 | + dbg("%s susp_count: %d", __func__, acm->susp_count); | |
212 | + if (acm->susp_count) { | |
213 | + acm->old_ready = acm->write_ready; | |
214 | + acm->delayed_wb = wb; | |
215 | + acm->write_ready = 0; | |
216 | + schedule_work(&acm->waker); | |
217 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
218 | + return 0; /* A white lie */ | |
219 | + } | |
220 | + usb_mark_last_busy(acm->dev); | |
221 | + | |
185 | 222 | if (!acm_wb_is_used(acm, wbn)) { |
186 | 223 | spin_unlock_irqrestore(&acm->write_lock, flags); |
187 | 224 | return 0; |
188 | 225 | } |
189 | - wb = &acm->wb[wbn]; | |
190 | 226 | |
191 | - if(acm_wb_is_avail(acm) <= 1) | |
192 | - acm->write_ready = 0; | |
227 | + rc = acm_start_wb(acm, wb); | |
193 | 228 | spin_unlock_irqrestore(&acm->write_lock, flags); |
194 | 229 | |
195 | - wb->urb->transfer_buffer = wb->buf; | |
196 | - wb->urb->transfer_dma = wb->dmah; | |
197 | - wb->urb->transfer_buffer_length = wb->len; | |
198 | - wb->urb->dev = acm->dev; | |
199 | - | |
200 | - if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { | |
201 | - dbg("usb_submit_urb(write bulk) failed: %d", rc); | |
202 | - acm_write_done(acm, wb); | |
203 | - } | |
204 | 230 | return rc; |
231 | + | |
205 | 232 | } |
206 | 233 | /* |
207 | 234 | * attributes exported through sysfs |
... | ... | @@ -304,6 +331,7 @@ |
304 | 331 | break; |
305 | 332 | } |
306 | 333 | exit: |
334 | + usb_mark_last_busy(acm->dev); | |
307 | 335 | retval = usb_submit_urb (urb, GFP_ATOMIC); |
308 | 336 | if (retval) |
309 | 337 | err ("%s - usb_submit_urb failed with result %d", |
310 | 338 | |
... | ... | @@ -320,8 +348,11 @@ |
320 | 348 | |
321 | 349 | dbg("Entering acm_read_bulk with status %d", status); |
322 | 350 | |
323 | - if (!ACM_READY(acm)) | |
351 | + if (!ACM_READY(acm)) { | |
352 | + dev_dbg(&acm->data->dev, "Aborting, acm not ready"); | |
324 | 353 | return; |
354 | + } | |
355 | + usb_mark_last_busy(acm->dev); | |
325 | 356 | |
326 | 357 | if (status) |
327 | 358 | dev_dbg(&acm->data->dev, "bulk rx status %d\n", status); |
... | ... | @@ -331,6 +362,7 @@ |
331 | 362 | |
332 | 363 | if (likely(status == 0)) { |
333 | 364 | spin_lock(&acm->read_lock); |
365 | + acm->processing++; | |
334 | 366 | list_add_tail(&rcv->list, &acm->spare_read_urbs); |
335 | 367 | list_add_tail(&buf->list, &acm->filled_read_bufs); |
336 | 368 | spin_unlock(&acm->read_lock); |
... | ... | @@ -343,7 +375,8 @@ |
343 | 375 | /* nevertheless the tasklet must be kicked unconditionally |
344 | 376 | so the queue cannot dry up */ |
345 | 377 | } |
346 | - tasklet_schedule(&acm->urb_task); | |
378 | + if (likely(!acm->susp_count)) | |
379 | + tasklet_schedule(&acm->urb_task); | |
347 | 380 | } |
348 | 381 | |
349 | 382 | static void acm_rx_tasklet(unsigned long _acm) |
350 | 383 | |
351 | 384 | |
352 | 385 | |
353 | 386 | |
... | ... | @@ -354,16 +387,23 @@ |
354 | 387 | struct acm_ru *rcv; |
355 | 388 | unsigned long flags; |
356 | 389 | unsigned char throttled; |
390 | + | |
357 | 391 | dbg("Entering acm_rx_tasklet"); |
358 | 392 | |
359 | 393 | if (!ACM_READY(acm)) |
394 | + { | |
395 | + dbg("acm_rx_tasklet: ACM not ready"); | |
360 | 396 | return; |
397 | + } | |
361 | 398 | |
362 | 399 | spin_lock_irqsave(&acm->throttle_lock, flags); |
363 | 400 | throttled = acm->throttle; |
364 | 401 | spin_unlock_irqrestore(&acm->throttle_lock, flags); |
365 | 402 | if (throttled) |
403 | + { | |
404 | + dbg("acm_rx_tasklet: throttled"); | |
366 | 405 | return; |
406 | + } | |
367 | 407 | |
368 | 408 | next_buffer: |
369 | 409 | spin_lock_irqsave(&acm->read_lock, flags); |
... | ... | @@ -403,6 +443,7 @@ |
403 | 443 | while (!list_empty(&acm->spare_read_bufs)) { |
404 | 444 | spin_lock_irqsave(&acm->read_lock, flags); |
405 | 445 | if (list_empty(&acm->spare_read_urbs)) { |
446 | + acm->processing = 0; | |
406 | 447 | spin_unlock_irqrestore(&acm->read_lock, flags); |
407 | 448 | return; |
408 | 449 | } |
409 | 450 | |
410 | 451 | |
411 | 452 | |
412 | 453 | |
413 | 454 | |
... | ... | @@ -425,18 +466,23 @@ |
425 | 466 | rcv->urb->transfer_dma = buf->dma; |
426 | 467 | rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
427 | 468 | |
428 | - dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); | |
429 | - | |
430 | 469 | /* This shouldn't kill the driver as unsuccessful URBs are returned to the |
431 | 470 | free-urbs-pool and resubmited ASAP */ |
432 | - if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { | |
471 | + spin_lock_irqsave(&acm->read_lock, flags); | |
472 | + if (acm->susp_count || usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { | |
433 | 473 | list_add(&buf->list, &acm->spare_read_bufs); |
434 | - spin_lock_irqsave(&acm->read_lock, flags); | |
435 | 474 | list_add(&rcv->list, &acm->spare_read_urbs); |
475 | + acm->processing = 0; | |
436 | 476 | spin_unlock_irqrestore(&acm->read_lock, flags); |
437 | 477 | return; |
478 | + } else { | |
479 | + spin_unlock_irqrestore(&acm->read_lock, flags); | |
480 | + dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); | |
438 | 481 | } |
439 | 482 | } |
483 | + spin_lock_irqsave(&acm->read_lock, flags); | |
484 | + acm->processing = 0; | |
485 | + spin_unlock_irqrestore(&acm->read_lock, flags); | |
440 | 486 | } |
441 | 487 | |
442 | 488 | /* data interface wrote those outgoing bytes */ |
... | ... | @@ -463,6 +509,27 @@ |
463 | 509 | tty_wakeup(acm->tty); |
464 | 510 | } |
465 | 511 | |
512 | +static void acm_waker(struct work_struct *waker) | |
513 | +{ | |
514 | + struct acm *acm = container_of(waker, struct acm, waker); | |
515 | + long flags; | |
516 | + int rv; | |
517 | + | |
518 | + rv = usb_autopm_get_interface(acm->control); | |
519 | + if (rv < 0) { | |
520 | + err("Autopm failure in %s", __func__); | |
521 | + return; | |
522 | + } | |
523 | + if (acm->delayed_wb) { | |
524 | + acm_start_wb(acm, acm->delayed_wb); | |
525 | + acm->delayed_wb = NULL; | |
526 | + } | |
527 | + spin_lock_irqsave(&acm->write_lock, flags); | |
528 | + acm->write_ready = acm->old_ready; | |
529 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
530 | + usb_autopm_put_interface(acm->control); | |
531 | +} | |
532 | + | |
466 | 533 | /* |
467 | 534 | * TTY handlers |
468 | 535 | */ |
... | ... | @@ -492,6 +559,8 @@ |
492 | 559 | |
493 | 560 | if (usb_autopm_get_interface(acm->control) < 0) |
494 | 561 | goto early_bail; |
562 | + else | |
563 | + acm->control->needs_remote_wakeup = 1; | |
495 | 564 | |
496 | 565 | mutex_lock(&acm->mutex); |
497 | 566 | if (acm->used++) { |
... | ... | @@ -509,6 +578,7 @@ |
509 | 578 | if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) && |
510 | 579 | (acm->ctrl_caps & USB_CDC_CAP_LINE)) |
511 | 580 | goto full_bailout; |
581 | + usb_autopm_put_interface(acm->control); | |
512 | 582 | |
513 | 583 | INIT_LIST_HEAD(&acm->spare_read_urbs); |
514 | 584 | INIT_LIST_HEAD(&acm->spare_read_bufs); |
515 | 585 | |
... | ... | @@ -570,12 +640,14 @@ |
570 | 640 | mutex_lock(&open_mutex); |
571 | 641 | if (!--acm->used) { |
572 | 642 | if (acm->dev) { |
643 | + usb_autopm_get_interface(acm->control); | |
573 | 644 | acm_set_control(acm, acm->ctrlout = 0); |
574 | 645 | usb_kill_urb(acm->ctrlurb); |
575 | 646 | for (i = 0; i < ACM_NW; i++) |
576 | 647 | usb_kill_urb(acm->wb[i].urb); |
577 | 648 | for (i = 0; i < nr; i++) |
578 | 649 | usb_kill_urb(acm->ru[i].urb); |
650 | + acm->control->needs_remote_wakeup = 0; | |
579 | 651 | usb_autopm_put_interface(acm->control); |
580 | 652 | } else |
581 | 653 | acm_tty_unregister(acm); |
... | ... | @@ -987,6 +1059,7 @@ |
987 | 1059 | acm->urb_task.func = acm_rx_tasklet; |
988 | 1060 | acm->urb_task.data = (unsigned long) acm; |
989 | 1061 | INIT_WORK(&acm->work, acm_softint); |
1062 | + INIT_WORK(&acm->waker, acm_waker); | |
990 | 1063 | spin_lock_init(&acm->throttle_lock); |
991 | 1064 | spin_lock_init(&acm->write_lock); |
992 | 1065 | spin_lock_init(&acm->read_lock); |
... | ... | @@ -1116,6 +1189,7 @@ |
1116 | 1189 | static void stop_data_traffic(struct acm *acm) |
1117 | 1190 | { |
1118 | 1191 | int i; |
1192 | + dbg("Entering stop_data_traffic"); | |
1119 | 1193 | |
1120 | 1194 | tasklet_disable(&acm->urb_task); |
1121 | 1195 | |
... | ... | @@ -1128,6 +1202,7 @@ |
1128 | 1202 | tasklet_enable(&acm->urb_task); |
1129 | 1203 | |
1130 | 1204 | cancel_work_sync(&acm->work); |
1205 | + cancel_work_sync(&acm->waker); | |
1131 | 1206 | } |
1132 | 1207 | |
1133 | 1208 | static void acm_disconnect(struct usb_interface *intf) |
1134 | 1209 | |
... | ... | @@ -1181,8 +1256,27 @@ |
1181 | 1256 | static int acm_suspend(struct usb_interface *intf, pm_message_t message) |
1182 | 1257 | { |
1183 | 1258 | struct acm *acm = usb_get_intfdata(intf); |
1259 | + int cnt; | |
1184 | 1260 | |
1185 | - if (acm->susp_count++) | |
1261 | + if (acm->dev->auto_pm) { | |
1262 | + int b; | |
1263 | + | |
1264 | + spin_lock_irq(&acm->read_lock); | |
1265 | + spin_lock(&acm->write_lock); | |
1266 | + b = acm->processing + acm->transmitting; | |
1267 | + spin_unlock(&acm->write_lock); | |
1268 | + spin_unlock_irq(&acm->read_lock); | |
1269 | + if (b) | |
1270 | + return -EBUSY; | |
1271 | + } | |
1272 | + | |
1273 | + spin_lock_irq(&acm->read_lock); | |
1274 | + spin_lock(&acm->write_lock); | |
1275 | + cnt = acm->susp_count++; | |
1276 | + spin_unlock(&acm->write_lock); | |
1277 | + spin_unlock_irq(&acm->read_lock); | |
1278 | + | |
1279 | + if (cnt) | |
1186 | 1280 | return 0; |
1187 | 1281 | /* |
1188 | 1282 | we treat opened interfaces differently, |
1189 | 1283 | |
1190 | 1284 | |
... | ... | @@ -1201,15 +1295,21 @@ |
1201 | 1295 | { |
1202 | 1296 | struct acm *acm = usb_get_intfdata(intf); |
1203 | 1297 | int rv = 0; |
1298 | + int cnt; | |
1204 | 1299 | |
1205 | - if (--acm->susp_count) | |
1300 | + spin_lock_irq(&acm->read_lock); | |
1301 | + acm->susp_count -= 1; | |
1302 | + cnt = acm->susp_count; | |
1303 | + spin_unlock_irq(&acm->read_lock); | |
1304 | + | |
1305 | + if (cnt) | |
1206 | 1306 | return 0; |
1207 | 1307 | |
1208 | 1308 | mutex_lock(&acm->mutex); |
1209 | 1309 | if (acm->used) { |
1210 | 1310 | rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); |
1211 | 1311 | if (rv < 0) |
1212 | - goto err_out; | |
1312 | + goto err_out; | |
1213 | 1313 | |
1214 | 1314 | tasklet_schedule(&acm->urb_task); |
1215 | 1315 | } |
drivers/usb/class/cdc-acm.h
... | ... | @@ -107,10 +107,14 @@ |
107 | 107 | struct list_head filled_read_bufs; |
108 | 108 | int write_used; /* number of non-empty write buffers */ |
109 | 109 | int write_ready; /* write urb is not running */ |
110 | + int old_ready; | |
111 | + int processing; | |
112 | + int transmitting; | |
110 | 113 | spinlock_t write_lock; |
111 | 114 | struct mutex mutex; |
112 | 115 | struct usb_cdc_line_coding line; /* bits, stop, parity */ |
113 | 116 | struct work_struct work; /* work queue entry for line discipline waking up */ |
117 | + struct work_struct waker; | |
114 | 118 | struct tasklet_struct urb_task; /* rx processing */ |
115 | 119 | spinlock_t throttle_lock; /* synchronize throtteling and read callback */ |
116 | 120 | unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ |
... | ... | @@ -123,6 +127,7 @@ |
123 | 127 | unsigned char clocal; /* termios CLOCAL */ |
124 | 128 | unsigned int ctrl_caps; /* control capabilities from the class specific header */ |
125 | 129 | unsigned int susp_count; /* number of suspended interfaces */ |
130 | + struct acm_wb *delayed_wb; /* write queued for a device about to be woken */ | |
126 | 131 | }; |
127 | 132 | |
128 | 133 | #define CDC_DATA_INTERFACE_TYPE 0x0a |