[rft]aggressive autosuspend for cdc-ether

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

in the glorious tradition of completely untested patches, here comes a patch
implementing autosuspend for always online connections using cdc-ether.
Please comment and test.

	Regards
		Oliver

commit 454621df41a08baaafad86bd151dba24f784cfe2
Author: Oliver Neukum <oneukum@linux-d698.(none)>
Date:   Sun May 3 11:03:56 2009 +0200

    autosuspenf for cdc-ether compatible with always online device

diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
index 55e8ecc..4a6eaa2 100644
--- a/drivers/net/usb/cdc_ether.c
+++ b/drivers/net/usb/cdc_ether.c
@@ -442,6 +442,12 @@ static int cdc_bind(struct usbnet *dev, struct usb_interface *intf)
 	return 0;
 }
 
+static int cdc_manage_power(struct usbnet *dev, int on)
+{
+	dev->intf->needs_remote_wakeup = on;
+	return 0;
+}
+
 static const struct driver_info	cdc_info = {
 	.description =	"CDC Ethernet Device",
 	.flags =	FLAG_ETHER,
@@ -449,6 +455,7 @@ static const struct driver_info	cdc_info = {
 	.bind =		cdc_bind,
 	.unbind =	usbnet_cdc_unbind,
 	.status =	cdc_status,
+	.manage_power =	cdc_manage_power,
 };
 
 /*-------------------------------------------------------------------------*/
@@ -576,6 +583,7 @@ static struct usb_driver cdc_driver = {
 	.disconnect =	usbnet_disconnect,
 	.suspend =	usbnet_suspend,
 	.resume =	usbnet_resume,
+	.supports_autosuspend = 1,
 };
 
 
diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index f3a2fce..3a09777 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -544,6 +544,7 @@ EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs);
 int usbnet_stop (struct net_device *net)
 {
 	struct usbnet		*dev = netdev_priv(net);
+	struct driver_info	*info = dev->driver_info;
 	int			temp;
 	DECLARE_WAIT_QUEUE_HEAD_ONSTACK (unlink_wakeup);
 	DECLARE_WAITQUEUE (wait, current);
@@ -581,7 +582,10 @@ int usbnet_stop (struct net_device *net)
 	dev->flags = 0;
 	del_timer_sync (&dev->delay);
 	tasklet_kill (&dev->bh);
-	usb_autopm_put_interface(dev->intf);
+	if (info->manage_power)
+		info->manage_power(dev, 0);
+	else
+		usb_autopm_put_interface(dev->intf);
 
 	return 0;
 }
@@ -662,6 +666,13 @@ int usbnet_open (struct net_device *net)
 
 	// delay posting reads until we're fully open
 	tasklet_schedule (&dev->bh);
+
+	if (info->manage_power) {
+		retval = info->manage_power(dev, 1);
+		if (retval < 0)
+			goto done;
+		usb_autopm_put_interface(dev->intf);
+	}
 	return retval;
 done:
 	usb_autopm_put_interface(dev->intf);
@@ -787,6 +798,12 @@ kevent (struct work_struct *work)
 		container_of(work, struct usbnet, kevent);
 	int			status;
 
+	if (test_bit(EVENT_DEV_WAKING, &dev->flags)) {
+		status = usb_autopm_get_interface(dev->intf);
+		clear_bit(EVENT_DEV_WAKING, &dev->flags);
+		if (!status)
+			usb_autopm_put_interface(dev->intf);
+	}
 	/* usb_clear_halt() needs a thread context */
 	if (test_bit (EVENT_TX_HALT, &dev->flags)) {
 		unlink_urbs (dev, &dev->txq);
@@ -862,11 +879,13 @@ static void tx_complete (struct urb *urb)
 	if (urb->status == 0) {
 		dev->stats.tx_packets++;
 		dev->stats.tx_bytes += entry->length;
+		usb_mark_last_busy(dev->udev);
 	} else {
 		dev->stats.tx_errors++;
 
 		switch (urb->status) {
 		case -EPIPE:
+			usb_mark_last_busy(dev->udev);
 			usbnet_defer_kevent (dev, EVENT_TX_HALT);
 			break;
 
@@ -880,6 +899,7 @@ static void tx_complete (struct urb *urb)
 		case -EPROTO:
 		case -ETIME:
 		case -EILSEQ:
+			usb_mark_last_busy(dev->udev);
 			if (!timer_pending (&dev->delay)) {
 				mod_timer (&dev->delay,
 					jiffies + THROTTLE_JIFFIES);
@@ -890,6 +910,7 @@ static void tx_complete (struct urb *urb)
 			netif_stop_queue (dev->net);
 			break;
 		default:
+			usb_mark_last_busy(dev->udev);
 			if (netif_msg_tx_err (dev))
 				devdbg (dev, "tx err %d", entry->urb->status);
 			break;
@@ -967,20 +988,32 @@ int usbnet_start_xmit (struct sk_buff *skb, struct net_device *net)
 
 	spin_lock_irqsave (&dev->txq.lock, flags);
 
-	switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
-	case -EPIPE:
-		netif_stop_queue (net);
-		usbnet_defer_kevent (dev, EVENT_TX_HALT);
-		break;
-	default:
-		if (netif_msg_tx_err (dev))
-			devdbg (dev, "tx: submit urb err %d", retval);
-		break;
-	case 0:
-		net->trans_start = jiffies;
-		__skb_queue_tail (&dev->txq, skb);
-		if (dev->txq.qlen >= TX_QLEN (dev))
+	if (!test_bit (EVENT_DEV_ASLEEP, &dev->flags)) {
+		switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
+		case -EPIPE:
 			netif_stop_queue (net);
+			usbnet_defer_kevent (dev, EVENT_TX_HALT);
+			break;
+		default:
+			if (netif_msg_tx_err (dev))
+				devdbg (dev, "tx: submit urb err %d", retval);
+			break;
+		case 0:
+			net->trans_start = jiffies;
+			__skb_queue_tail (&dev->txq, skb);
+			if (dev->txq.qlen >= TX_QLEN (dev))
+				netif_stop_queue (net);
+		}
+	} else {
+		/*
+		 * everything is prepared
+		 * the URB is store until the resumption method submits it
+		 * as it is easier to store only one urb, the queue is
+		 * stopped
+		 */
+		dev->deferred = urb;
+		usbnet_defer_kevent(dev, EVENT_DEV_WAKING);
+		netif_stop_queue(net);
 	}
 	spin_unlock_irqrestore (&dev->txq.lock, flags);
 
@@ -1278,6 +1311,15 @@ int usbnet_suspend (struct usb_interface *intf, pm_message_t message)
 	struct usbnet		*dev = usb_get_intfdata(intf);
 
 	if (!dev->suspend_count++) {
+		spin_lock_irq(&dev->txq.lock);
+		/* don't autosuspend while transmitting */
+		if (dev->txq.qlen && (message.event & PM_EVENT_AUTO)) {
+			spin_unlock_irq(&dev->txq.lock);
+			return -EBUSY;
+		} else {
+			set_bit(EVENT_DEV_ASLEEP, &dev->flags);
+			spin_unlock_irq(&dev->txq.lock);
+		}
 		/*
 		 * accelerate emptying of the rx and queues, to avoid
 		 * having everything error out.
@@ -1298,9 +1340,31 @@ EXPORT_SYMBOL_GPL(usbnet_suspend);
 int usbnet_resume (struct usb_interface *intf)
 {
 	struct usbnet		*dev = usb_get_intfdata(intf);
+	struct sk_buff 		*skb;
+	struct urb		*res;
+	int			retval;
 
-	if (!--dev->suspend_count)
+	if (!--dev->suspend_count) {
+		spin_lock_irq(&dev->txq.lock);
+		res = dev->deferred;
+		dev->deferred = NULL;
+		clear_bit(EVENT_DEV_ASLEEP, &dev->flags);
+		spin_unlock_irq(&dev->txq.lock);
+		if (res) {
+			retval = usb_submit_urb(res, GFP_NOIO);
+			if (retval < 0) {
+				usb_free_urb(res);
+				netif_start_queue(dev->net);
+			} else {
+				skb = (struct sk_buff *)res->context;
+				dev->net->trans_start = jiffies;
+				__skb_queue_tail (&dev->txq, skb);
+				if (!(dev->txq.qlen >= TX_QLEN(dev)))
+					netif_start_queue(dev->net);
+			}
+		}
 		tasklet_schedule (&dev->bh);
+	}
 
 	return 0;
 }
diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h
index 36fabb9..d14ec35 100644
--- a/include/linux/usb/usbnet.h
+++ b/include/linux/usb/usbnet.h
@@ -55,6 +55,7 @@ struct usbnet {
 	struct sk_buff_head	txq;
 	struct sk_buff_head	done;
 	struct urb		*interrupt;
+	struct urb		*deferred;
 	struct tasklet_struct	bh;
 
 	struct work_struct	kevent;
@@ -64,6 +65,8 @@ struct usbnet {
 #		define EVENT_RX_MEMORY	2
 #		define EVENT_STS_SPLIT	3
 #		define EVENT_LINK_RESET	4
+#		define EVENT_DEV_WAKING 5
+#		define EVENT_DEV_ASLEEP 6
 };
 
 static inline struct usb_driver *driver_of(struct usb_interface *intf)
@@ -101,6 +104,9 @@ struct driver_info {
 	/* see if peer is connected ... can sleep */
 	int	(*check_connect)(struct usbnet *);
 
+	/* (dis)activate runtime power management */
+	int	(*manage_power)(struct usbnet *, int);
+
 	/* for status polling */
 	void	(*status)(struct usbnet *, struct urb *);
 

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux