]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
cdc-acm: introduce a cool down
authorOliver Neukum <oneukum@suse.com>
Wed, 15 Apr 2020 15:13:58 +0000 (17:13 +0200)
committerStefan Bader <stefan.bader@canonical.com>
Thu, 14 May 2020 08:54:15 +0000 (10:54 +0200)
BugLink: https://bugs.launchpad.net/bugs/1877461
commit a4e7279cd1d19f48f0af2a10ed020febaa9ac092 upstream.

Immediate submission in case of a babbling device can lead
to a busy loop. Introducing a delayed work.

Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: stable <stable@vger.kernel.org>
Tested-by: Jonas Karlsson <jonas.karlsson@actia.se>
Link: https://lore.kernel.org/r/20200415151358.32664-2-oneukum@suse.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
drivers/usb/class/cdc-acm.c
drivers/usb/class/cdc-acm.h

index 99dbac666239c9ab186d1cf1ceea9d72b8fa24ac..4d330227d43911cdd99a54186d999463757dc9b7 100644 (file)
@@ -411,9 +411,12 @@ static void acm_ctrl_irq(struct urb *urb)
 
 exit:
        retval = usb_submit_urb(urb, GFP_ATOMIC);
-       if (retval && retval != -EPERM)
+       if (retval && retval != -EPERM && retval != -ENODEV)
                dev_err(&acm->control->dev,
                        "%s - usb_submit_urb failed: %d\n", __func__, retval);
+       else
+               dev_vdbg(&acm->control->dev,
+                       "control resubmission terminated %d\n", retval);
 }
 
 static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags)
@@ -429,6 +432,8 @@ static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags)
                        dev_err(&acm->data->dev,
                                "urb %d failed submission with %d\n",
                                index, res);
+               } else {
+                       dev_vdbg(&acm->data->dev, "intended failure %d\n", res);
                }
                set_bit(index, &acm->read_urbs_free);
                return res;
@@ -471,6 +476,7 @@ static void acm_read_bulk_callback(struct urb *urb)
        int status = urb->status;
        bool stopped = false;
        bool stalled = false;
+       bool cooldown = false;
 
        dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n",
                rb->index, urb->actual_length, status);
@@ -497,6 +503,14 @@ static void acm_read_bulk_callback(struct urb *urb)
                        __func__, status);
                stopped = true;
                break;
+       case -EOVERFLOW:
+       case -EPROTO:
+               dev_dbg(&acm->data->dev,
+                       "%s - cooling babbling device\n", __func__);
+               usb_mark_last_busy(acm->dev);
+               set_bit(rb->index, &acm->urbs_in_error_delay);
+               cooldown = true;
+               break;
        default:
                dev_dbg(&acm->data->dev,
                        "%s - nonzero urb status received: %d\n",
@@ -518,9 +532,11 @@ static void acm_read_bulk_callback(struct urb *urb)
         */
        smp_mb__after_atomic();
 
-       if (stopped || stalled) {
+       if (stopped || stalled || cooldown) {
                if (stalled)
                        schedule_work(&acm->work);
+               else if (cooldown)
+                       schedule_delayed_work(&acm->dwork, HZ / 2);
                return;
        }
 
@@ -572,6 +588,12 @@ static void acm_softint(struct work_struct *work)
                }
        }
 
+       if (test_and_clear_bit(ACM_ERROR_DELAY, &acm->flags)) {
+               for (i = 0; i < ACM_NR; i++)
+                       if (test_and_clear_bit(i, &acm->urbs_in_error_delay))
+                                       acm_submit_read_urb(acm, i, GFP_NOIO);
+       }
+
        if (test_and_clear_bit(EVENT_TTY_WAKEUP, &acm->flags))
                tty_port_tty_wakeup(&acm->port);
 }
@@ -1361,6 +1383,7 @@ made_compressed_probe:
        acm->readsize = readsize;
        acm->rx_buflimit = num_rx_buf;
        INIT_WORK(&acm->work, acm_softint);
+       INIT_DELAYED_WORK(&acm->dwork, acm_softint);
        init_waitqueue_head(&acm->wioctl);
        spin_lock_init(&acm->write_lock);
        spin_lock_init(&acm->read_lock);
@@ -1574,6 +1597,7 @@ static void acm_disconnect(struct usb_interface *intf)
 
        acm_kill_urbs(acm);
        cancel_work_sync(&acm->work);
+       cancel_delayed_work_sync(&acm->dwork);
 
        tty_unregister_device(acm_tty_driver, acm->minor);
 
@@ -1616,6 +1640,8 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message)
 
        acm_kill_urbs(acm);
        cancel_work_sync(&acm->work);
+       cancel_delayed_work_sync(&acm->dwork);
+       acm->urbs_in_error_delay = 0;
 
        return 0;
 }
index 515aad0847ee84b408e7fceb503081f5d97fc78b..30380d28a504b8715417f6f720103ea480ab562a 100644 (file)
@@ -108,8 +108,11 @@ struct acm {
        unsigned long flags;
 #              define EVENT_TTY_WAKEUP 0
 #              define EVENT_RX_STALL   1
+#              define ACM_ERROR_DELAY  3
+       unsigned long urbs_in_error_delay;              /* these need to be restarted after a delay */
        struct usb_cdc_line_coding line;                /* bits, stop, parity */
-       struct work_struct work;                        /* work queue entry for line discipline waking up */
+       struct work_struct work;                        /* work queue entry for various purposes*/
+       struct delayed_work dwork;                      /* for cool downs needed in error recovery */
        unsigned int ctrlin;                            /* input control lines (DCD, DSR, RI, break, overruns) */
        unsigned int ctrlout;                           /* output control lines (DTR, RTS) */
        struct async_icount iocount;                    /* counters for control line changes */