diff options
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 92 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-acm.h | 2 | 
2 files changed, 79 insertions, 15 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 912d97aaf9b..b0f9873ce6b 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -496,10 +496,19 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)  	   otherwise it is scheduled, and with high data rates data can get lost. */  	tty->low_latency = 1; +	if (usb_autopm_get_interface(acm->control)) { +		mutex_unlock(&open_mutex); +		return -EIO; +	} + +	mutex_lock(&acm->mutex); +	mutex_unlock(&open_mutex);  	if (acm->used++) { +		usb_autopm_put_interface(acm->control);  		goto done;          } +  	acm->ctrlurb->dev = acm->dev;  	if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {  		dbg("usb_submit_urb(ctrl irq) failed"); @@ -526,14 +535,15 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)  done:  err_out: -	mutex_unlock(&open_mutex); +	mutex_unlock(&acm->mutex);  	return rv;  full_bailout:  	usb_kill_urb(acm->ctrlurb);  bail_out: +	usb_autopm_put_interface(acm->control);  	acm->used--; -	mutex_unlock(&open_mutex); +	mutex_unlock(&acm->mutex);  	return -EIO;  } @@ -570,6 +580,7 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)  			usb_kill_urb(acm->writeurb);  			for (i = 0; i < nr; i++)  				usb_kill_urb(acm->ru[i].urb); +			usb_autopm_put_interface(acm->control);  		} else  			acm_tty_unregister(acm);  	} @@ -980,6 +991,7 @@ skip_normal_probe:  	spin_lock_init(&acm->throttle_lock);  	spin_lock_init(&acm->write_lock);  	spin_lock_init(&acm->read_lock); +	mutex_init(&acm->mutex);  	acm->write_ready = 1;  	acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); @@ -1096,6 +1108,25 @@ alloc_fail:  	return -ENOMEM;  } +static void stop_data_traffic(struct acm *acm) +{ +	int i; + +	tasklet_disable(&acm->urb_task); + +	usb_kill_urb(acm->ctrlurb); +	usb_kill_urb(acm->writeurb); +	for (i = 0; i < acm->rx_buflimit; i++) +		usb_kill_urb(acm->ru[i].urb); + +	INIT_LIST_HEAD(&acm->filled_read_bufs); +	INIT_LIST_HEAD(&acm->spare_read_bufs); + +	tasklet_enable(&acm->urb_task); + +	cancel_work_sync(&acm->work); +} +  static void acm_disconnect(struct usb_interface *intf)  {  	struct acm *acm = usb_get_intfdata(intf); @@ -1123,19 +1154,7 @@ static void acm_disconnect(struct usb_interface *intf)  	usb_set_intfdata(acm->control, NULL);  	usb_set_intfdata(acm->data, NULL); -	tasklet_disable(&acm->urb_task); - -	usb_kill_urb(acm->ctrlurb); -	usb_kill_urb(acm->writeurb); -	for (i = 0; i < acm->rx_buflimit; i++) -		usb_kill_urb(acm->ru[i].urb); - -	INIT_LIST_HEAD(&acm->filled_read_bufs); -	INIT_LIST_HEAD(&acm->spare_read_bufs); - -	tasklet_enable(&acm->urb_task); - -	flush_scheduled_work(); /* wait for acm_softint */ +	stop_data_traffic(acm);  	acm_write_buffers_free(acm);  	usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); @@ -1156,6 +1175,46 @@ static void acm_disconnect(struct usb_interface *intf)  		tty_hangup(acm->tty);  } +static int acm_suspend(struct usb_interface *intf, pm_message_t message) +{ +	struct acm *acm = usb_get_intfdata(intf); + +	if (acm->susp_count++) +		return 0; +	/* +	we treat opened interfaces differently, +	we must guard against open +	*/ +	mutex_lock(&acm->mutex); + +	if (acm->used) +		stop_data_traffic(acm); + +	mutex_unlock(&acm->mutex); +	return 0; +} + +static int acm_resume(struct usb_interface *intf) +{ +	struct acm *acm = usb_get_intfdata(intf); +	int rv = 0; + +	if (--acm->susp_count) +		return 0; + +	mutex_lock(&acm->mutex); +	if (acm->used) { +		rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); +		if (rv < 0) +		goto err_out; + +		tasklet_schedule(&acm->urb_task); +	} + +err_out: +	mutex_unlock(&acm->mutex); +	return rv; +}  /*   * USB driver structure.   */ @@ -1208,7 +1267,10 @@ static struct usb_driver acm_driver = {  	.name =		"cdc_acm",  	.probe =	acm_probe,  	.disconnect =	acm_disconnect, +	.suspend =	acm_suspend, +	.resume =	acm_resume,  	.id_table =	acm_ids, +	.supports_autosuspend = 1,  };  /* diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 09f7765dbf8..8df6a57dcf9 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -107,6 +107,7 @@ struct acm {  	int write_used;					/* number of non-empty write buffers */  	int write_ready;				/* write urb is not running */  	spinlock_t write_lock; +	struct mutex mutex;  	struct usb_cdc_line_coding line;		/* bits, stop, parity */  	struct work_struct work;			/* work queue entry for line discipline waking up */  	struct tasklet_struct urb_task;                 /* rx processing */ @@ -120,6 +121,7 @@ struct acm {  	unsigned char throttle;				/* throttled by tty layer */  	unsigned char clocal;				/* termios CLOCAL */  	unsigned int ctrl_caps;				/* control capabilities from the class specific header */ +	unsigned int susp_count;			/* number of suspended interfaces */  };  #define CDC_DATA_INTERFACE_TYPE	0x0a  |