forked from mirrors/linux
		
	USB: additional power savings for cdc-acm devices that support remote wakeup
this patch saves power for cdc-acm devices that support remote wakeup while the device is connected. - request needs_remote_wakeup when needed - delayed write while a device is autoresumed - the device is marked busy when appropriate Signed-off-by: Oliver Neukum <oneukum@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
		
							parent
							
								
									188d636027
								
							
						
					
					
						commit
						11ea859d64
					
				
					 2 changed files with 126 additions and 21 deletions
				
			
		|  | @ -159,12 +159,34 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb) | |||
| 	spin_lock_irqsave(&acm->write_lock, flags); | ||||
| 	acm->write_ready = 1; | ||||
| 	wb->use = 0; | ||||
| 	acm->transmitting--; | ||||
| 	spin_unlock_irqrestore(&acm->write_lock, flags); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Poke write. | ||||
|  * | ||||
|  * the caller is responsible for locking | ||||
|  */ | ||||
| 
 | ||||
| static int acm_start_wb(struct acm *acm, struct acm_wb *wb) | ||||
| { | ||||
| 	int rc; | ||||
| 
 | ||||
| 	acm->transmitting++; | ||||
| 
 | ||||
| 	wb->urb->transfer_buffer = wb->buf; | ||||
| 	wb->urb->transfer_dma = wb->dmah; | ||||
| 	wb->urb->transfer_buffer_length = wb->len; | ||||
| 	wb->urb->dev = acm->dev; | ||||
| 
 | ||||
| 	if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { | ||||
| 		dbg("usb_submit_urb(write bulk) failed: %d", rc); | ||||
| 		acm_write_done(acm, wb); | ||||
| 	} | ||||
| 	return rc; | ||||
| } | ||||
| 
 | ||||
| static int acm_write_start(struct acm *acm, int wbn) | ||||
| { | ||||
| 	unsigned long flags; | ||||
|  | @ -182,26 +204,31 @@ static int acm_write_start(struct acm *acm, int wbn) | |||
| 		return 0;	/* A white lie */ | ||||
| 	} | ||||
| 
 | ||||
| 	wb = &acm->wb[wbn]; | ||||
| 	if(acm_wb_is_avail(acm) <= 1) | ||||
| 		acm->write_ready = 0; | ||||
| 
 | ||||
| 	dbg("%s susp_count: %d", __func__, acm->susp_count); | ||||
| 	if (acm->susp_count) { | ||||
| 		acm->old_ready = acm->write_ready; | ||||
| 		acm->delayed_wb = wb; | ||||
| 		acm->write_ready = 0; | ||||
| 		schedule_work(&acm->waker); | ||||
| 		spin_unlock_irqrestore(&acm->write_lock, flags); | ||||
| 		return 0;	/* A white lie */ | ||||
| 	} | ||||
| 	usb_mark_last_busy(acm->dev); | ||||
| 
 | ||||
| 	if (!acm_wb_is_used(acm, wbn)) { | ||||
| 		spin_unlock_irqrestore(&acm->write_lock, flags); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	wb = &acm->wb[wbn]; | ||||
| 
 | ||||
| 	if(acm_wb_is_avail(acm) <= 1) | ||||
| 		acm->write_ready = 0; | ||||
| 	rc = acm_start_wb(acm, wb); | ||||
| 	spin_unlock_irqrestore(&acm->write_lock, flags); | ||||
| 
 | ||||
| 	wb->urb->transfer_buffer = wb->buf; | ||||
| 	wb->urb->transfer_dma = wb->dmah; | ||||
| 	wb->urb->transfer_buffer_length = wb->len; | ||||
| 	wb->urb->dev = acm->dev; | ||||
| 
 | ||||
| 	if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { | ||||
| 		dbg("usb_submit_urb(write bulk) failed: %d", rc); | ||||
| 		acm_write_done(acm, wb); | ||||
| 	} | ||||
| 	return rc; | ||||
| 
 | ||||
| } | ||||
| /*
 | ||||
|  * attributes exported through sysfs | ||||
|  | @ -304,6 +331,7 @@ static void acm_ctrl_irq(struct urb *urb) | |||
| 			break; | ||||
| 	} | ||||
| exit: | ||||
| 	usb_mark_last_busy(acm->dev); | ||||
| 	retval = usb_submit_urb (urb, GFP_ATOMIC); | ||||
| 	if (retval) | ||||
| 		err ("%s - usb_submit_urb failed with result %d", | ||||
|  | @ -320,8 +348,11 @@ static void acm_read_bulk(struct urb *urb) | |||
| 
 | ||||
| 	dbg("Entering acm_read_bulk with status %d", status); | ||||
| 
 | ||||
| 	if (!ACM_READY(acm)) | ||||
| 	if (!ACM_READY(acm)) { | ||||
| 		dev_dbg(&acm->data->dev, "Aborting, acm not ready"); | ||||
| 		return; | ||||
| 	} | ||||
| 	usb_mark_last_busy(acm->dev); | ||||
| 
 | ||||
| 	if (status) | ||||
| 		dev_dbg(&acm->data->dev, "bulk rx status %d\n", status); | ||||
|  | @ -331,6 +362,7 @@ static void acm_read_bulk(struct urb *urb) | |||
| 
 | ||||
| 	if (likely(status == 0)) { | ||||
| 		spin_lock(&acm->read_lock); | ||||
| 		acm->processing++; | ||||
| 		list_add_tail(&rcv->list, &acm->spare_read_urbs); | ||||
| 		list_add_tail(&buf->list, &acm->filled_read_bufs); | ||||
| 		spin_unlock(&acm->read_lock); | ||||
|  | @ -343,6 +375,7 @@ static void acm_read_bulk(struct urb *urb) | |||
| 		/* nevertheless the tasklet must be kicked unconditionally
 | ||||
| 		so the queue cannot dry up */ | ||||
| 	} | ||||
| 	if (likely(!acm->susp_count)) | ||||
| 		tasklet_schedule(&acm->urb_task); | ||||
| } | ||||
| 
 | ||||
|  | @ -354,16 +387,23 @@ static void acm_rx_tasklet(unsigned long _acm) | |||
| 	struct acm_ru *rcv; | ||||
| 	unsigned long flags; | ||||
| 	unsigned char throttled; | ||||
| 
 | ||||
| 	dbg("Entering acm_rx_tasklet"); | ||||
| 
 | ||||
| 	if (!ACM_READY(acm)) | ||||
| 	{ | ||||
| 		dbg("acm_rx_tasklet: ACM not ready"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	spin_lock_irqsave(&acm->throttle_lock, flags); | ||||
| 	throttled = acm->throttle; | ||||
| 	spin_unlock_irqrestore(&acm->throttle_lock, flags); | ||||
| 	if (throttled) | ||||
| 	{ | ||||
| 		dbg("acm_rx_tasklet: throttled"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| next_buffer: | ||||
| 	spin_lock_irqsave(&acm->read_lock, flags); | ||||
|  | @ -403,6 +443,7 @@ static void acm_rx_tasklet(unsigned long _acm) | |||
| 	while (!list_empty(&acm->spare_read_bufs)) { | ||||
| 		spin_lock_irqsave(&acm->read_lock, flags); | ||||
| 		if (list_empty(&acm->spare_read_urbs)) { | ||||
| 			acm->processing = 0; | ||||
| 			spin_unlock_irqrestore(&acm->read_lock, flags); | ||||
| 			return; | ||||
| 		} | ||||
|  | @ -425,18 +466,23 @@ static void acm_rx_tasklet(unsigned long _acm) | |||
| 		rcv->urb->transfer_dma = buf->dma; | ||||
| 		rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; | ||||
| 
 | ||||
| 		dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); | ||||
| 
 | ||||
| 		/* This shouldn't kill the driver as unsuccessful URBs are returned to the
 | ||||
| 		   free-urbs-pool and resubmited ASAP */ | ||||
| 		if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { | ||||
| 			list_add(&buf->list, &acm->spare_read_bufs); | ||||
| 		spin_lock_irqsave(&acm->read_lock, flags); | ||||
| 		if (acm->susp_count || usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { | ||||
| 			list_add(&buf->list, &acm->spare_read_bufs); | ||||
| 			list_add(&rcv->list, &acm->spare_read_urbs); | ||||
| 			acm->processing = 0; | ||||
| 			spin_unlock_irqrestore(&acm->read_lock, flags); | ||||
| 			return; | ||||
| 		} else { | ||||
| 			spin_unlock_irqrestore(&acm->read_lock, flags); | ||||
| 			dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); | ||||
| 		} | ||||
| 	} | ||||
| 	spin_lock_irqsave(&acm->read_lock, flags); | ||||
| 	acm->processing = 0; | ||||
| 	spin_unlock_irqrestore(&acm->read_lock, flags); | ||||
| } | ||||
| 
 | ||||
| /* data interface wrote those outgoing bytes */ | ||||
|  | @ -463,6 +509,27 @@ static void acm_softint(struct work_struct *work) | |||
| 	tty_wakeup(acm->tty); | ||||
| } | ||||
| 
 | ||||
| static void acm_waker(struct work_struct *waker) | ||||
| { | ||||
| 	struct acm *acm = container_of(waker, struct acm, waker); | ||||
| 	long flags; | ||||
| 	int rv; | ||||
| 
 | ||||
| 	rv = usb_autopm_get_interface(acm->control); | ||||
| 	if (rv < 0) { | ||||
| 		err("Autopm failure in %s", __func__); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (acm->delayed_wb) { | ||||
| 		acm_start_wb(acm, acm->delayed_wb); | ||||
| 		acm->delayed_wb = NULL; | ||||
| 	} | ||||
| 	spin_lock_irqsave(&acm->write_lock, flags); | ||||
| 	acm->write_ready = acm->old_ready; | ||||
| 	spin_unlock_irqrestore(&acm->write_lock, flags); | ||||
| 	usb_autopm_put_interface(acm->control); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * TTY handlers | ||||
|  */ | ||||
|  | @ -492,6 +559,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) | |||
| 
 | ||||
| 	if (usb_autopm_get_interface(acm->control) < 0) | ||||
| 		goto early_bail; | ||||
| 	else | ||||
| 		acm->control->needs_remote_wakeup = 1; | ||||
| 
 | ||||
| 	mutex_lock(&acm->mutex); | ||||
| 	if (acm->used++) { | ||||
|  | @ -509,6 +578,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) | |||
| 	if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) && | ||||
| 	    (acm->ctrl_caps & USB_CDC_CAP_LINE)) | ||||
| 		goto full_bailout; | ||||
| 	usb_autopm_put_interface(acm->control); | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&acm->spare_read_urbs); | ||||
| 	INIT_LIST_HEAD(&acm->spare_read_bufs); | ||||
|  | @ -570,12 +640,14 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) | |||
| 	mutex_lock(&open_mutex); | ||||
| 	if (!--acm->used) { | ||||
| 		if (acm->dev) { | ||||
| 			usb_autopm_get_interface(acm->control); | ||||
| 			acm_set_control(acm, acm->ctrlout = 0); | ||||
| 			usb_kill_urb(acm->ctrlurb); | ||||
| 			for (i = 0; i < ACM_NW; i++) | ||||
| 				usb_kill_urb(acm->wb[i].urb); | ||||
| 			for (i = 0; i < nr; i++) | ||||
| 				usb_kill_urb(acm->ru[i].urb); | ||||
| 			acm->control->needs_remote_wakeup = 0; | ||||
| 			usb_autopm_put_interface(acm->control); | ||||
| 		} else | ||||
| 			acm_tty_unregister(acm); | ||||
|  | @ -987,6 +1059,7 @@ static int acm_probe (struct usb_interface *intf, | |||
| 	acm->urb_task.func = acm_rx_tasklet; | ||||
| 	acm->urb_task.data = (unsigned long) acm; | ||||
| 	INIT_WORK(&acm->work, acm_softint); | ||||
| 	INIT_WORK(&acm->waker, acm_waker); | ||||
| 	spin_lock_init(&acm->throttle_lock); | ||||
| 	spin_lock_init(&acm->write_lock); | ||||
| 	spin_lock_init(&acm->read_lock); | ||||
|  | @ -1116,6 +1189,7 @@ static int acm_probe (struct usb_interface *intf, | |||
| static void stop_data_traffic(struct acm *acm) | ||||
| { | ||||
| 	int i; | ||||
| 	dbg("Entering stop_data_traffic"); | ||||
| 
 | ||||
| 	tasklet_disable(&acm->urb_task); | ||||
| 
 | ||||
|  | @ -1128,6 +1202,7 @@ static void stop_data_traffic(struct acm *acm) | |||
| 	tasklet_enable(&acm->urb_task); | ||||
| 
 | ||||
| 	cancel_work_sync(&acm->work); | ||||
| 	cancel_work_sync(&acm->waker); | ||||
| } | ||||
| 
 | ||||
| static void acm_disconnect(struct usb_interface *intf) | ||||
|  | @ -1181,8 +1256,27 @@ static void acm_disconnect(struct usb_interface *intf) | |||
| static int acm_suspend(struct usb_interface *intf, pm_message_t message) | ||||
| { | ||||
| 	struct acm *acm = usb_get_intfdata(intf); | ||||
| 	int cnt; | ||||
| 
 | ||||
| 	if (acm->susp_count++) | ||||
| 	if (acm->dev->auto_pm) { | ||||
| 		int b; | ||||
| 
 | ||||
| 		spin_lock_irq(&acm->read_lock); | ||||
| 		spin_lock(&acm->write_lock); | ||||
| 		b = acm->processing + acm->transmitting; | ||||
| 		spin_unlock(&acm->write_lock); | ||||
| 		spin_unlock_irq(&acm->read_lock); | ||||
| 		if (b) | ||||
| 			return -EBUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	spin_lock_irq(&acm->read_lock); | ||||
| 	spin_lock(&acm->write_lock); | ||||
| 	cnt = acm->susp_count++; | ||||
| 	spin_unlock(&acm->write_lock); | ||||
| 	spin_unlock_irq(&acm->read_lock); | ||||
| 
 | ||||
| 	if (cnt) | ||||
| 		return 0; | ||||
| 	/*
 | ||||
| 	we treat opened interfaces differently, | ||||
|  | @ -1201,8 +1295,14 @@ static int acm_resume(struct usb_interface *intf) | |||
| { | ||||
| 	struct acm *acm = usb_get_intfdata(intf); | ||||
| 	int rv = 0; | ||||
| 	int cnt; | ||||
| 
 | ||||
| 	if (--acm->susp_count) | ||||
| 	spin_lock_irq(&acm->read_lock); | ||||
| 	acm->susp_count -= 1; | ||||
| 	cnt = acm->susp_count; | ||||
| 	spin_unlock_irq(&acm->read_lock); | ||||
| 
 | ||||
| 	if (cnt) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	mutex_lock(&acm->mutex); | ||||
|  |  | |||
|  | @ -107,10 +107,14 @@ struct acm { | |||
| 	struct list_head filled_read_bufs; | ||||
| 	int write_used;					/* number of non-empty write buffers */ | ||||
| 	int write_ready;				/* write urb is not running */ | ||||
| 	int old_ready; | ||||
| 	int processing; | ||||
| 	int transmitting; | ||||
| 	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 work_struct waker; | ||||
| 	struct tasklet_struct urb_task;                 /* rx processing */ | ||||
| 	spinlock_t throttle_lock;			/* synchronize throtteling and read callback */ | ||||
| 	unsigned int ctrlin;				/* input control lines (DCD, DSR, RI, break, overruns) */ | ||||
|  | @ -123,6 +127,7 @@ struct acm { | |||
| 	unsigned char clocal;				/* termios CLOCAL */ | ||||
| 	unsigned int ctrl_caps;				/* control capabilities from the class specific header */ | ||||
| 	unsigned int susp_count;			/* number of suspended interfaces */ | ||||
| 	struct acm_wb *delayed_wb;			/* write queued for a device about to be woken */ | ||||
| }; | ||||
| 
 | ||||
| #define CDC_DATA_INTERFACE_TYPE	0x0a | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Oliver Neukum
						Oliver Neukum