diff options
Diffstat (limited to 'drivers/usb/class')
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 36 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-acm.h | 5 | 
2 files changed, 35 insertions, 6 deletions
| diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 84d6f7df09a4..ded8d93834ca 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -412,9 +412,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) @@ -430,6 +433,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;  	} @@ -557,14 +573,20 @@ static void acm_softint(struct work_struct *work)  	struct acm *acm = container_of(work, struct acm, work);  	if (test_bit(EVENT_RX_STALL, &acm->flags)) { -		if (!(usb_autopm_get_interface(acm->data))) { +		smp_mb(); /* against acm_suspend() */ +		if (!acm->susp_count) {  			for (i = 0; i < acm->rx_buflimit; i++)  				usb_kill_urb(acm->read_urbs[i]);  			usb_clear_halt(acm->dev, acm->in);  			acm_submit_read_urbs(acm, GFP_KERNEL); -			usb_autopm_put_interface(acm->data); +			clear_bit(EVENT_RX_STALL, &acm->flags);  		} -		clear_bit(EVENT_RX_STALL, &acm->flags); +	} + +	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)) @@ -1333,6 +1355,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); @@ -1542,6 +1565,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); @@ -1584,6 +1608,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;  } diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index ca1c026382c2..cd5e9d8ab237 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -109,8 +109,11 @@ struct acm {  #		define EVENT_TTY_WAKEUP	0  #		define EVENT_RX_STALL	1  #		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 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 */ |