cdc-wdm: untangle a circular dependency between callback and softint
authorOliver Neukum <oneukum@suse.com>
Mon, 26 Apr 2021 09:26:22 +0000 (11:26 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 10 May 2021 12:39:51 +0000 (14:39 +0200)
We have a cycle of callbacks scheduling works which submit
URBs with those callbacks. This needs to be blocked, stopped
and unblocked to untangle the circle.

Signed-off-by: Oliver Neukum <oneukum@suse.com>
Link: https://lore.kernel.org/r/20210426092622.20433-1-oneukum@suse.com
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/class/cdc-wdm.c

index 508b1c3..d1e4a73 100644 (file)
@@ -321,12 +321,23 @@ exit:
 
 }
 
-static void kill_urbs(struct wdm_device *desc)
+static void poison_urbs(struct wdm_device *desc)
 {
        /* the order here is essential */
-       usb_kill_urb(desc->command);
-       usb_kill_urb(desc->validity);
-       usb_kill_urb(desc->response);
+       usb_poison_urb(desc->command);
+       usb_poison_urb(desc->validity);
+       usb_poison_urb(desc->response);
+}
+
+static void unpoison_urbs(struct wdm_device *desc)
+{
+       /*
+        *  the order here is not essential
+        *  it is symmetrical just to be nice
+        */
+       usb_unpoison_urb(desc->response);
+       usb_unpoison_urb(desc->validity);
+       usb_unpoison_urb(desc->command);
 }
 
 static void free_urbs(struct wdm_device *desc)
@@ -741,11 +752,12 @@ static int wdm_release(struct inode *inode, struct file *file)
        if (!desc->count) {
                if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
                        dev_dbg(&desc->intf->dev, "wdm_release: cleanup\n");
-                       kill_urbs(desc);
+                       poison_urbs(desc);
                        spin_lock_irq(&desc->iuspin);
                        desc->resp_count = 0;
                        spin_unlock_irq(&desc->iuspin);
                        desc->manage_power(desc->intf, 0);
+                       unpoison_urbs(desc);
                } else {
                        /* must avoid dev_printk here as desc->intf is invalid */
                        pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
@@ -1037,9 +1049,9 @@ static void wdm_disconnect(struct usb_interface *intf)
        wake_up_all(&desc->wait);
        mutex_lock(&desc->rlock);
        mutex_lock(&desc->wlock);
+       poison_urbs(desc);
        cancel_work_sync(&desc->rxwork);
        cancel_work_sync(&desc->service_outs_intr);
-       kill_urbs(desc);
        mutex_unlock(&desc->wlock);
        mutex_unlock(&desc->rlock);
 
@@ -1080,9 +1092,10 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
                set_bit(WDM_SUSPENDING, &desc->flags);
                spin_unlock_irq(&desc->iuspin);
                /* callback submits work - order is essential */
-               kill_urbs(desc);
+               poison_urbs(desc);
                cancel_work_sync(&desc->rxwork);
                cancel_work_sync(&desc->service_outs_intr);
+               unpoison_urbs(desc);
        }
        if (!PMSG_IS_AUTO(message)) {
                mutex_unlock(&desc->wlock);
@@ -1140,7 +1153,7 @@ static int wdm_pre_reset(struct usb_interface *intf)
        wake_up_all(&desc->wait);
        mutex_lock(&desc->rlock);
        mutex_lock(&desc->wlock);
-       kill_urbs(desc);
+       poison_urbs(desc);
        cancel_work_sync(&desc->rxwork);
        cancel_work_sync(&desc->service_outs_intr);
        return 0;
@@ -1151,6 +1164,7 @@ static int wdm_post_reset(struct usb_interface *intf)
        struct wdm_device *desc = wdm_find_device(intf);
        int rv;
 
+       unpoison_urbs(desc);
        clear_bit(WDM_OVERFLOW, &desc->flags);
        clear_bit(WDM_RESETTING, &desc->flags);
        rv = recover_from_urb_loss(desc);