forked from mirrors/linux
		
	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: 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>
This commit is contained in:
		
							parent
							
								
									0afccd7601
								
							
						
					
					
						commit
						a4e7279cd1
					
				
					 2 changed files with 32 additions and 3 deletions
				
			
		| 
						 | 
					@ -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)
 | 
				
			||||||
			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)
 | 
				
			||||||
	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)
 | 
				
			||||||
			__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)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	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)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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,
 | 
				
			||||||
	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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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 {
 | 
				
			||||||
#		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 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue