]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/usb/class/cdc-acm.c
USB: cdc-acm: fix unthrottle races
[mirror_ubuntu-bionic-kernel.git] / drivers / usb / class / cdc-acm.c
index 96af023cc95c3305a3a51b0315398bddd0dc9d74..ec4faf58d0f3a07ff5fc6cff25b42a46229d9dea 100644 (file)
@@ -469,12 +469,12 @@ static void acm_read_bulk_callback(struct urb *urb)
        struct acm *acm = rb->instance;
        unsigned long flags;
        int status = urb->status;
+       bool stopped = false;
+       bool stalled = false;
 
        dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n",
                rb->index, urb->actual_length, status);
 
-       set_bit(rb->index, &acm->read_urbs_free);
-
        if (!acm->dev) {
                dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__);
                return;
@@ -487,15 +487,16 @@ static void acm_read_bulk_callback(struct urb *urb)
                break;
        case -EPIPE:
                set_bit(EVENT_RX_STALL, &acm->flags);
-               schedule_work(&acm->work);
-               return;
+               stalled = true;
+               break;
        case -ENOENT:
        case -ECONNRESET:
        case -ESHUTDOWN:
                dev_dbg(&acm->data->dev,
                        "%s - urb shutting down with status: %d\n",
                        __func__, status);
-               return;
+               stopped = true;
+               break;
        default:
                dev_dbg(&acm->data->dev,
                        "%s - nonzero urb status received: %d\n",
@@ -504,10 +505,24 @@ static void acm_read_bulk_callback(struct urb *urb)
        }
 
        /*
-        * Unthrottle may run on another CPU which needs to see events
-        * in the same order. Submission has an implict barrier
+        * Make sure URB processing is done before marking as free to avoid
+        * racing with unthrottle() on another CPU. Matches the barriers
+        * implied by the test_and_clear_bit() in acm_submit_read_urb().
         */
        smp_mb__before_atomic();
+       set_bit(rb->index, &acm->read_urbs_free);
+       /*
+        * Make sure URB is marked as free before checking the throttled flag
+        * to avoid racing with unthrottle() on another CPU. Matches the
+        * smp_mb() in unthrottle().
+        */
+       smp_mb__after_atomic();
+
+       if (stopped || stalled) {
+               if (stalled)
+                       schedule_work(&acm->work);
+               return;
+       }
 
        /* throttle device if requested by tty */
        spin_lock_irqsave(&acm->read_lock, flags);
@@ -841,6 +856,9 @@ static void acm_tty_unthrottle(struct tty_struct *tty)
        acm->throttle_req = 0;
        spin_unlock_irq(&acm->read_lock);
 
+       /* Matches the smp_mb__after_atomic() in acm_read_bulk_callback(). */
+       smp_mb();
+
        if (was_throttled)
                acm_submit_read_urbs(acm, GFP_KERNEL);
 }