diff options
author | Bjørn Mork <bjorn@mork.no> | 2013-12-20 08:07:24 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-12-20 15:06:46 -0500 |
commit | 8dd5cd5395b90070d98149d0a94e5981a74cd2ec (patch) | |
tree | e4c61bc41c7b2c580fedc55056cfa74939500958 /drivers/usb/class | |
parent | bee53637e5836a7cc642ca036e44bd77caf7bc35 (diff) |
usb: cdc-wdm: avoid hanging on zero length reads
commit 73e06865ead1 ("USB: cdc-wdm: support back-to-back
USB_CDC_NOTIFY_RESPONSE_AVAILABLE notifications") implemented
queued response handling. This added a new requirement: The read
urb must be resubmitted every time we clear the WDM_READ flag if
the response counter indicates that the device is waiting for a
read.
Fix by factoring out the code handling the WMD_READ clearing and
possible urb submission, calling it everywhere we clear the flag.
Without this fix, the driver ends up in a state where the read urb
is inactive, but the response counter is positive after a zero
length read. This prevents the read urb from ever being submitted
again and the driver appears to be hanging.
Fixes: 73e06865ead1 ("USB: cdc-wdm: support back-to-back USB_CDC_NOTIFY_RESPONSE_AVAILABLE notifications")
Cc: Greg Suarez <gsuarez@smithmicro.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Cc: stable <stable@vger.kernel.org> # 3.13
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/class')
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 70 |
1 files changed, 38 insertions, 32 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 4d387596f3f0..750afa9774b4 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c | |||
@@ -432,6 +432,38 @@ outnl: | |||
432 | return rv < 0 ? rv : count; | 432 | return rv < 0 ? rv : count; |
433 | } | 433 | } |
434 | 434 | ||
435 | /* | ||
436 | * clear WDM_READ flag and possibly submit the read urb if resp_count | ||
437 | * is non-zero. | ||
438 | * | ||
439 | * Called with desc->iuspin locked | ||
440 | */ | ||
441 | static int clear_wdm_read_flag(struct wdm_device *desc) | ||
442 | { | ||
443 | int rv = 0; | ||
444 | |||
445 | clear_bit(WDM_READ, &desc->flags); | ||
446 | |||
447 | /* submit read urb only if the device is waiting for it */ | ||
448 | if (!--desc->resp_count) | ||
449 | goto out; | ||
450 | |||
451 | set_bit(WDM_RESPONDING, &desc->flags); | ||
452 | spin_unlock_irq(&desc->iuspin); | ||
453 | rv = usb_submit_urb(desc->response, GFP_KERNEL); | ||
454 | spin_lock_irq(&desc->iuspin); | ||
455 | if (rv) { | ||
456 | dev_err(&desc->intf->dev, | ||
457 | "usb_submit_urb failed with result %d\n", rv); | ||
458 | |||
459 | /* make sure the next notification trigger a submit */ | ||
460 | clear_bit(WDM_RESPONDING, &desc->flags); | ||
461 | desc->resp_count = 0; | ||
462 | } | ||
463 | out: | ||
464 | return rv; | ||
465 | } | ||
466 | |||
435 | static ssize_t wdm_read | 467 | static ssize_t wdm_read |
436 | (struct file *file, char __user *buffer, size_t count, loff_t *ppos) | 468 | (struct file *file, char __user *buffer, size_t count, loff_t *ppos) |
437 | { | 469 | { |
@@ -503,8 +535,10 @@ retry: | |||
503 | 535 | ||
504 | if (!desc->reslength) { /* zero length read */ | 536 | if (!desc->reslength) { /* zero length read */ |
505 | dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); | 537 | dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); |
506 | clear_bit(WDM_READ, &desc->flags); | 538 | rv = clear_wdm_read_flag(desc); |
507 | spin_unlock_irq(&desc->iuspin); | 539 | spin_unlock_irq(&desc->iuspin); |
540 | if (rv < 0) | ||
541 | goto err; | ||
508 | goto retry; | 542 | goto retry; |
509 | } | 543 | } |
510 | cntr = desc->length; | 544 | cntr = desc->length; |
@@ -526,37 +560,9 @@ retry: | |||
526 | 560 | ||
527 | desc->length -= cntr; | 561 | desc->length -= cntr; |
528 | /* in case we had outstanding data */ | 562 | /* in case we had outstanding data */ |
529 | if (!desc->length) { | 563 | if (!desc->length) |
530 | clear_bit(WDM_READ, &desc->flags); | 564 | clear_wdm_read_flag(desc); |
531 | 565 | spin_unlock_irq(&desc->iuspin); | |
532 | if (--desc->resp_count) { | ||
533 | set_bit(WDM_RESPONDING, &desc->flags); | ||
534 | spin_unlock_irq(&desc->iuspin); | ||
535 | |||
536 | rv = usb_submit_urb(desc->response, GFP_KERNEL); | ||
537 | if (rv) { | ||
538 | dev_err(&desc->intf->dev, | ||
539 | "%s: usb_submit_urb failed with result %d\n", | ||
540 | __func__, rv); | ||
541 | spin_lock_irq(&desc->iuspin); | ||
542 | clear_bit(WDM_RESPONDING, &desc->flags); | ||
543 | spin_unlock_irq(&desc->iuspin); | ||
544 | |||
545 | if (rv == -ENOMEM) { | ||
546 | rv = schedule_work(&desc->rxwork); | ||
547 | if (rv) | ||
548 | dev_err(&desc->intf->dev, "Cannot schedule work\n"); | ||
549 | } else { | ||
550 | spin_lock_irq(&desc->iuspin); | ||
551 | desc->resp_count = 0; | ||
552 | spin_unlock_irq(&desc->iuspin); | ||
553 | } | ||
554 | } | ||
555 | } else | ||
556 | spin_unlock_irq(&desc->iuspin); | ||
557 | } else | ||
558 | spin_unlock_irq(&desc->iuspin); | ||
559 | |||
560 | rv = cntr; | 566 | rv = cntr; |
561 | 567 | ||
562 | err: | 568 | err: |