Commit a4e7279c authored by Oliver Neukum's avatar Oliver Neukum Committed by Greg Kroah-Hartman

cdc-acm: introduce a cool down

Immediate submission in case of a babbling device can lead
to a busy loop. Introducing a delayed work.
Signed-off-by: default avatarOliver Neukum <oneukum@suse.com>
Cc: stable <stable@vger.kernel.org>
Tested-by: default avatarJonas Karlsson <jonas.karlsson@actia.se>
Link: https://lore.kernel.org/r/20200415151358.32664-2-oneukum@suse.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 0afccd76
...@@ -412,9 +412,12 @@ static void acm_ctrl_irq(struct urb *urb) ...@@ -412,9 +412,12 @@ static void acm_ctrl_irq(struct urb *urb)
exit: exit:
retval = usb_submit_urb(urb, GFP_ATOMIC); retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval && retval != -EPERM) if (retval && retval != -EPERM && retval != -ENODEV)
dev_err(&acm->control->dev, dev_err(&acm->control->dev,
"%s - usb_submit_urb failed: %d\n", __func__, retval); "%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) static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags)
...@@ -430,6 +433,8 @@ static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags) ...@@ -430,6 +433,8 @@ static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags)
dev_err(&acm->data->dev, dev_err(&acm->data->dev,
"urb %d failed submission with %d\n", "urb %d failed submission with %d\n",
index, res); index, res);
} else {
dev_vdbg(&acm->data->dev, "intended failure %d\n", res);
} }
set_bit(index, &acm->read_urbs_free); set_bit(index, &acm->read_urbs_free);
return res; return res;
...@@ -471,6 +476,7 @@ static void acm_read_bulk_callback(struct urb *urb) ...@@ -471,6 +476,7 @@ static void acm_read_bulk_callback(struct urb *urb)
int status = urb->status; int status = urb->status;
bool stopped = false; bool stopped = false;
bool stalled = false; bool stalled = false;
bool cooldown = false;
dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n", dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n",
rb->index, urb->actual_length, status); rb->index, urb->actual_length, status);
...@@ -497,6 +503,14 @@ static void acm_read_bulk_callback(struct urb *urb) ...@@ -497,6 +503,14 @@ static void acm_read_bulk_callback(struct urb *urb)
__func__, status); __func__, status);
stopped = true; stopped = true;
break; 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: default:
dev_dbg(&acm->data->dev, dev_dbg(&acm->data->dev,
"%s - nonzero urb status received: %d\n", "%s - nonzero urb status received: %d\n",
...@@ -518,9 +532,11 @@ static void acm_read_bulk_callback(struct urb *urb) ...@@ -518,9 +532,11 @@ static void acm_read_bulk_callback(struct urb *urb)
*/ */
smp_mb__after_atomic(); smp_mb__after_atomic();
if (stopped || stalled) { if (stopped || stalled || cooldown) {
if (stalled) if (stalled)
schedule_work(&acm->work); schedule_work(&acm->work);
else if (cooldown)
schedule_delayed_work(&acm->dwork, HZ / 2);
return; return;
} }
...@@ -567,6 +583,12 @@ static void acm_softint(struct work_struct *work) ...@@ -567,6 +583,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)) if (test_and_clear_bit(EVENT_TTY_WAKEUP, &acm->flags))
tty_port_tty_wakeup(&acm->port); tty_port_tty_wakeup(&acm->port);
} }
...@@ -1333,6 +1355,7 @@ static int acm_probe(struct usb_interface *intf, ...@@ -1333,6 +1355,7 @@ static int acm_probe(struct usb_interface *intf,
acm->readsize = readsize; acm->readsize = readsize;
acm->rx_buflimit = num_rx_buf; acm->rx_buflimit = num_rx_buf;
INIT_WORK(&acm->work, acm_softint); INIT_WORK(&acm->work, acm_softint);
INIT_DELAYED_WORK(&acm->dwork, acm_softint);
init_waitqueue_head(&acm->wioctl); init_waitqueue_head(&acm->wioctl);
spin_lock_init(&acm->write_lock); spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock); spin_lock_init(&acm->read_lock);
...@@ -1542,6 +1565,7 @@ static void acm_disconnect(struct usb_interface *intf) ...@@ -1542,6 +1565,7 @@ static void acm_disconnect(struct usb_interface *intf)
acm_kill_urbs(acm); acm_kill_urbs(acm);
cancel_work_sync(&acm->work); cancel_work_sync(&acm->work);
cancel_delayed_work_sync(&acm->dwork);
tty_unregister_device(acm_tty_driver, acm->minor); tty_unregister_device(acm_tty_driver, acm->minor);
...@@ -1584,6 +1608,8 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message) ...@@ -1584,6 +1608,8 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message)
acm_kill_urbs(acm); acm_kill_urbs(acm);
cancel_work_sync(&acm->work); cancel_work_sync(&acm->work);
cancel_delayed_work_sync(&acm->dwork);
acm->urbs_in_error_delay = 0;
return 0; return 0;
} }
......
...@@ -109,8 +109,11 @@ struct acm { ...@@ -109,8 +109,11 @@ struct acm {
# define EVENT_TTY_WAKEUP 0 # define EVENT_TTY_WAKEUP 0
# define EVENT_RX_STALL 1 # define EVENT_RX_STALL 1
# define ACM_THROTTLED 2 # define ACM_THROTTLED 2
# 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 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 ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
unsigned int ctrlout; /* output control lines (DTR, RTS) */ unsigned int ctrlout; /* output control lines (DTR, RTS) */
struct async_icount iocount; /* counters for control line changes */ struct async_icount iocount; /* counters for control line changes */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment