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 *);