diff options
author | Johan Hovold <jhovold@gmail.com> | 2014-05-26 13:23:37 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-05-27 18:04:09 -0400 |
commit | e144ed28bed10684f9aaec6325ed974d53f76110 (patch) | |
tree | bac73dbef9647f9c5af0f1d852e1c89fe838c982 /drivers/usb/class | |
parent | 5a345c20c17d87099224a4be12e69e5bd7023dca (diff) |
USB: cdc-acm: fix write and resume race
Fix race between write() and resume() due to improper locking that could
lead to writes being reordered.
Resume must be done atomically and susp_count be protected by the
write_lock in order to prevent racing with write(). This could otherwise
lead to writes being reordered if write() grabs the write_lock after
susp_count is decremented, but before the delayed urb is submitted.
Fixes: 11ea859d64b6 ("USB: additional power savings for cdc-acm devices
that support remote wakeup")
Cc: <stable@vger.kernel.org> # v2.6.27
Signed-off-by: Johan Hovold <jhovold@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/class')
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 23 |
1 files changed, 9 insertions, 14 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 3bd4226c13dc..e72a657a6177 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c | |||
@@ -1541,27 +1541,20 @@ static int acm_resume(struct usb_interface *intf) | |||
1541 | struct acm *acm = usb_get_intfdata(intf); | 1541 | struct acm *acm = usb_get_intfdata(intf); |
1542 | struct acm_wb *wb; | 1542 | struct acm_wb *wb; |
1543 | int rv = 0; | 1543 | int rv = 0; |
1544 | int cnt; | ||
1545 | 1544 | ||
1546 | spin_lock_irq(&acm->read_lock); | 1545 | spin_lock_irq(&acm->read_lock); |
1547 | acm->susp_count -= 1; | 1546 | spin_lock(&acm->write_lock); |
1548 | cnt = acm->susp_count; | ||
1549 | spin_unlock_irq(&acm->read_lock); | ||
1550 | 1547 | ||
1551 | if (cnt) | 1548 | if (--acm->susp_count) |
1552 | return 0; | 1549 | goto out; |
1553 | 1550 | ||
1554 | if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) { | 1551 | if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) { |
1555 | rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); | 1552 | rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC); |
1556 | 1553 | ||
1557 | spin_lock_irq(&acm->write_lock); | ||
1558 | if (acm->delayed_wb) { | 1554 | if (acm->delayed_wb) { |
1559 | wb = acm->delayed_wb; | 1555 | wb = acm->delayed_wb; |
1560 | acm->delayed_wb = NULL; | 1556 | acm->delayed_wb = NULL; |
1561 | spin_unlock_irq(&acm->write_lock); | ||
1562 | acm_start_wb(acm, wb); | 1557 | acm_start_wb(acm, wb); |
1563 | } else { | ||
1564 | spin_unlock_irq(&acm->write_lock); | ||
1565 | } | 1558 | } |
1566 | 1559 | ||
1567 | /* | 1560 | /* |
@@ -1569,12 +1562,14 @@ static int acm_resume(struct usb_interface *intf) | |||
1569 | * do the write path at all cost | 1562 | * do the write path at all cost |
1570 | */ | 1563 | */ |
1571 | if (rv < 0) | 1564 | if (rv < 0) |
1572 | goto err_out; | 1565 | goto out; |
1573 | 1566 | ||
1574 | rv = acm_submit_read_urbs(acm, GFP_NOIO); | 1567 | rv = acm_submit_read_urbs(acm, GFP_ATOMIC); |
1575 | } | 1568 | } |
1569 | out: | ||
1570 | spin_unlock(&acm->write_lock); | ||
1571 | spin_unlock_irq(&acm->read_lock); | ||
1576 | 1572 | ||
1577 | err_out: | ||
1578 | return rv; | 1573 | return rv; |
1579 | } | 1574 | } |
1580 | 1575 | ||