summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJohan Hovold <jhovold@gmail.com>2014-05-26 19:23:37 +0200
committerBen Hutchings <ben@decadent.org.uk>2014-07-11 13:33:41 +0100
commit161427758577d5b5299c0b897730235a1fa950cc (patch)
tree43bbdedb497527f25b6f4077e803c7ecd7c651a1 /drivers
parent3627c07522db92848b8d6414616315028084f5f2 (diff)
USB: cdc-acm: fix write and resume race
commit e144ed28bed10684f9aaec6325ed974d53f76110 upstream. 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") Signed-off-by: Johan Hovold <jhovold@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> [bwh: Backported to 3.2: - Adjust context - Move mutex_lock(acm->mutex) above acquisition of spinlocks] Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/class/cdc-acm.c27
1 files changed, 11 insertions, 16 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index 456ec175e185..a54c0d00f5b7 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -1371,28 +1371,21 @@ static int acm_resume(struct usb_interface *intf)
struct acm *acm = usb_get_intfdata(intf);
struct acm_wb *wb;
int rv = 0;
- int cnt;
+ mutex_lock(&acm->mutex);
spin_lock_irq(&acm->read_lock);
- acm->susp_count -= 1;
- cnt = acm->susp_count;
- spin_unlock_irq(&acm->read_lock);
+ spin_lock(&acm->write_lock);
- if (cnt)
- return 0;
+ if (--acm->susp_count)
+ goto out;
- mutex_lock(&acm->mutex);
if (acm->port.count) {
- rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
+ rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC);
- spin_lock_irq(&acm->write_lock);
if (acm->delayed_wb) {
wb = acm->delayed_wb;
acm->delayed_wb = NULL;
- spin_unlock_irq(&acm->write_lock);
acm_start_wb(acm, wb);
- } else {
- spin_unlock_irq(&acm->write_lock);
}
/*
@@ -1400,13 +1393,15 @@ static int acm_resume(struct usb_interface *intf)
* do the write path at all cost
*/
if (rv < 0)
- goto err_out;
+ goto out;
- rv = acm_submit_read_urbs(acm, GFP_NOIO);
+ rv = acm_submit_read_urbs(acm, GFP_ATOMIC);
}
-
-err_out:
+out:
+ spin_unlock(&acm->write_lock);
+ spin_unlock_irq(&acm->read_lock);
mutex_unlock(&acm->mutex);
+
return rv;
}