OSDN Git Service

USB: serial: fix unthrottle races
[sagit-ice-cold/kernel_xiaomi_msm8998.git] / drivers / usb / serial / generic.c
index 101051d..faead4f 100644 (file)
@@ -350,6 +350,7 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
        struct usb_serial_port *port = urb->context;
        unsigned char *data = urb->transfer_buffer;
        unsigned long flags;
+       bool stopped = false;
        int status = urb->status;
        int i;
 
@@ -357,33 +358,51 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
                if (urb == port->read_urbs[i])
                        break;
        }
-       set_bit(i, &port->read_urbs_free);
 
        dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i,
                                                        urb->actual_length);
        switch (status) {
        case 0:
+               usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+                                                       data);
+               port->serial->type->process_read_urb(urb);
                break;
        case -ENOENT:
        case -ECONNRESET:
        case -ESHUTDOWN:
                dev_dbg(&port->dev, "%s - urb stopped: %d\n",
                                                        __func__, status);
-               return;
+               stopped = true;
+               break;
        case -EPIPE:
                dev_err(&port->dev, "%s - urb stopped: %d\n",
                                                        __func__, status);
-               return;
+               stopped = true;
+               break;
        default:
                dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
                                                        __func__, status);
-               goto resubmit;
+               break;
        }
 
-       usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
-       port->serial->type->process_read_urb(urb);
+       /*
+        * 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
+        * usb_serial_generic_submit_read_urb().
+        */
+       smp_mb__before_atomic();
+       set_bit(i, &port->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)
+               return;
 
-resubmit:
        /* Throttle the device if requested by tty */
        spin_lock_irqsave(&port->lock, flags);
        port->throttled = port->throttle_req;
@@ -458,6 +477,12 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty)
        port->throttled = port->throttle_req = 0;
        spin_unlock_irq(&port->lock);
 
+       /*
+        * Matches the smp_mb__after_atomic() in
+        * usb_serial_generic_read_bulk_callback().
+        */
+       smp_mb();
+
        if (was_throttled)
                usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
 }