From patchwork Fri Aug 28 13:54:55 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oliver Neukum X-Patchwork-Id: 32362 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@bilbo.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from ozlabs.org (ozlabs.org [203.10.76.45]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "mx.ozlabs.org", Issuer "CA Cert Signing Authority" (verified OK)) by bilbo.ozlabs.org (Postfix) with ESMTPS id 25B38B7BE6 for ; Fri, 28 Aug 2009 23:53:54 +1000 (EST) Received: by ozlabs.org (Postfix) id 053DFDDD0C; Fri, 28 Aug 2009 23:53:54 +1000 (EST) Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by ozlabs.org (Postfix) with ESMTP id 9487EDDD0B for ; Fri, 28 Aug 2009 23:53:52 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751582AbZH1Nxm (ORCPT ); Fri, 28 Aug 2009 09:53:42 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751568AbZH1Nxl (ORCPT ); Fri, 28 Aug 2009 09:53:41 -0400 Received: from smtp-out003.kontent.com ([81.88.40.217]:55950 "EHLO smtp-out003.kontent.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751387AbZH1Nxk (ORCPT ); Fri, 28 Aug 2009 09:53:40 -0400 Received: from linux-d698.localnet (p549A24FB.dip0.t-ipconnect.de [84.154.36.251]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) (Authenticated sender: neukum_org@smtp-out003.kontent.com) by smtp-out003.kontent.com (Postfix) with ESMTPSA id 68E1D400036B; Fri, 28 Aug 2009 15:53:41 +0200 (CEST) From: Oliver Neukum To: David Brownell , "Torgny Johansson" , netdev@vger.kernel.org, linux-usb@vger.kernel.org Subject: autosuspend for cdc-ether Date: Fri, 28 Aug 2009 15:54:55 +0200 User-Agent: KMail/1.10.3 (Linux/2.6.31-rc4-0.1-default; KDE/4.1.3; x86_64; ; ) MIME-Version: 1.0 Content-Disposition: inline Message-Id: <200908281554.55780.oliver@neukum.org> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Hi, this allows the use of remote wakeup to autosuspend online devices. What do you think? Regards Oliver Tested-by: Torgny Johansson --- commit 7f2d9a0440f6453462bd7bf95f77eda17c16d865 Author: Oliver Neukum Date: Fri Aug 28 15:49:15 2009 +0200 usb: usbnet: runtime power management for active connections Devices that support remote wakeup can be autosuspended even while the interface is up. Transmissions are queued and processed after the device has been woken up. -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index 4a6aff5..8ee5bd7 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -411,6 +411,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, @@ -418,6 +424,7 @@ static const struct driver_info cdc_info = { .bind = cdc_bind, .unbind = usbnet_cdc_unbind, .status = cdc_status, + .manage_power = cdc_manage_power, }; /*-------------------------------------------------------------------------*/ @@ -570,6 +577,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 edfd9e1..c21b3d2 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -575,6 +575,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); @@ -612,7 +613,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; } @@ -693,6 +697,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); @@ -822,6 +833,7 @@ kevent (struct work_struct *work) if (test_bit (EVENT_TX_HALT, &dev->flags)) { unlink_urbs (dev, &dev->txq); status = usb_clear_halt (dev->udev, dev->out); + usb_autopm_put_interface(dev->intf); if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) { @@ -893,17 +905,20 @@ static void tx_complete (struct urb *urb) if (urb->status == 0) { dev->net->stats.tx_packets++; dev->net->stats.tx_bytes += entry->length; + usb_autopm_put_interface_async(dev->intf); } else { dev->net->stats.tx_errors++; switch (urb->status) { case -EPIPE: + /* we do not allow autosuspension */ usbnet_defer_kevent (dev, EVENT_TX_HALT); break; /* software-driven interface shutdown */ case -ECONNRESET: // async unlink case -ESHUTDOWN: // hardware gone + usb_autopm_put_interface_async(dev->intf); break; // like rx, tx gets controller i/o faults during khubd delays @@ -911,6 +926,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); @@ -919,8 +935,10 @@ static void tx_complete (struct urb *urb) urb->status); } netif_stop_queue (dev->net); + usb_autopm_put_interface_async(dev->intf); break; default: + usb_autopm_put_interface_async(dev->intf); if (netif_msg_tx_err (dev)) devdbg (dev, "tx err %d", entry->urb->status); break; @@ -996,8 +1014,29 @@ int usbnet_start_xmit (struct sk_buff *skb, struct net_device *net) } } + + spin_lock_irqsave (&dev->txq.lock, flags); + retval = usb_autopm_get_interface_async(dev->intf); + if (retval < 0) { + spin_unlock_irqrestore (&dev->txq.lock, flags); + goto drop; + } + +#ifdef CONFIG_PM + /* if this triggers the device is still a sleep */ + if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) { + /* transmission will be done in resume */ + dev->deferred = urb; + /* no use to process more packets */ + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->txq.lock, flags); + retval = NET_XMIT_SUCCESS; + goto deferred; + } +#endif + switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { case -EPIPE: netif_stop_queue (net); @@ -1028,6 +1067,7 @@ drop: devdbg (dev, "> tx, len %d, type 0x%x", length, skb->protocol); } +deferred: return retval; } EXPORT_SYMBOL_GPL(usbnet_start_xmit); @@ -1303,6 +1343,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. @@ -1322,11 +1371,34 @@ EXPORT_SYMBOL_GPL(usbnet_suspend); int usbnet_resume (struct usb_interface *intf) { - struct usbnet *dev = usb_get_intfdata(intf); - - if (!--dev->suspend_count) + struct usbnet *dev = usb_get_intfdata(intf); + struct sk_buff *skb; + struct urb *res; + int retval; + + 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); + usb_autopm_put_interface_async(dev->intf); + } 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; } EXPORT_SYMBOL_GPL(usbnet_resume); diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index 310e18a..6fa0545 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -54,6 +54,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; @@ -63,6 +64,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) @@ -100,6 +103,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 *);