From patchwork Thu Oct 23 13:49:37 2008 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Per Hallsmark X-Patchwork-Id: 5469 X-Patchwork-Delegate: jgarzik@pobox.com Return-Path: X-Original-To: patchwork-incoming@ozlabs.org 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 CF052DDDFF for ; Fri, 24 Oct 2008 00:51:12 +1100 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751898AbYJWNvI (ORCPT ); Thu, 23 Oct 2008 09:51:08 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751890AbYJWNvG (ORCPT ); Thu, 23 Oct 2008 09:51:06 -0400 Received: from mail.t2data.se ([212.247.174.230]:3404 "EHLO mail.t2data.se" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751079AbYJWNvE (ORCPT ); Thu, 23 Oct 2008 09:51:04 -0400 X-MDAV-Processed: mail.t2data.se, Thu, 23 Oct 2008 15:50:59 +0200 X-Spam-Processed: mail.t2data.se, Thu, 23 Oct 2008 15:50:51 +0200 X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on oin.hqnet.t2data.se X-Spam-Level: ****** X-Spam-Status: No, score=6.3 required=10.0 tests=FM_MULTI_ODD2, IMPRONONCABLE_2, J_CHICKENPOX_56, J_CHICKENPOX_63, MR_NOT_ATTRIBUTED_IP, RATWR10_MESSID, TW_DB, TW_GF,TW_QL,TW_VD shortcircuit=no autolearn=disabled version=3.2.4 Received: from [172.16.123.10] by t2data.se (Cipher TLSv1:RC4-MD5:128) (MDaemon PRO v10.0.1) with ESMTP id md50000481061.msg; Thu, 23 Oct 2008 15:50:50 +0200 X-MDOP-RefID: str=0001.0A0B0209.49008136.0103, ss=1, fgs=0 (_st=1 _vt=0 _iwf=0) X-MDPtrLookup-Result: hardfail ip=212.247.174.226 (no PTR records found) (mail.t2data.se) X-MDHeloLookup-Result: hardfail smtp.helo=[172.16.123.10] (does not match 212.247.174.226) (mail.t2data.se) X-Authenticated-Sender: perh@t2data.se X-MDRemoteIP: 212.247.174.226 X-Return-Path: per.hallsmark@t2data.se X-Envelope-From: per.hallsmark@t2data.se Message-ID: <490080F1.1060107@t2data.se> Date: Thu, 23 Oct 2008 15:49:37 +0200 From: Per Hallsmark User-Agent: Thunderbird 2.0.0.16 (X11/20080723) MIME-Version: 1.0 To: linux-usb@vger.kernel.org CC: netdev@vger.kernel.org, david-b@pacbell.net Subject: [PATCH v2] usbnet: enable more aggressive autosuspend Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Enable more aggressive autosuspend in usbnet. Some commenting and cleanups done. Signed-off-by: Per Hallsmark diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 8463efb..a2f0c8b 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -87,6 +87,8 @@ static int msg_level = -1; module_param (msg_level, int, 0); MODULE_PARM_DESC (msg_level, "Override default message level"); +static void waker(struct work_struct *work); + /*-------------------------------------------------------------------------*/ /* handles CDC Ethernet and many other network "bulk data" interfaces */ @@ -325,6 +327,7 @@ static void rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags) if (netif_running (dev->net) && netif_device_present (dev->net) && !test_bit (EVENT_RX_HALT, &dev->flags)) { + usb_mark_last_busy(dev->udev); switch (retval = usb_submit_urb (urb, GFP_ATOMIC)) { case -EPIPE: usbnet_defer_kevent (dev, EVENT_RX_HALT); @@ -496,6 +499,7 @@ static void intr_complete (struct urb *urb) return; memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); + usb_mark_last_busy(dev->udev); status = usb_submit_urb (urb, GFP_ATOMIC); if (status != 0 && netif_msg_timer (dev)) deverr(dev, "intr resubmit --> %d", status); @@ -589,6 +593,10 @@ static int usbnet_stop (struct net_device *net) dev->flags = 0; del_timer_sync (&dev->delay); tasklet_kill (&dev->bh); + + dev->used--; + + dev->intf->needs_remote_wakeup = 0; usb_autopm_put_interface(dev->intf); return 0; @@ -669,6 +677,11 @@ static int usbnet_open (struct net_device *net) // delay posting reads until we're fully open tasklet_schedule (&dev->bh); + + dev->used++; + + dev->intf->needs_remote_wakeup = 1; + usb_autopm_put_interface(dev->intf); return retval; done: usb_autopm_put_interface(dev->intf); @@ -921,7 +934,7 @@ static void usbnet_tx_timeout (struct net_device *net) /*-------------------------------------------------------------------------*/ -static int usbnet_start_xmit (struct sk_buff *skb, struct net_device *net) +static int __usbnet_start_xmit (struct sk_buff *skb, struct net_device *net) { struct usbnet *dev = netdev_priv(net); int length; @@ -955,6 +968,7 @@ static int usbnet_start_xmit (struct sk_buff *skb, struct net_device *net) entry->state = tx_start; entry->length = length; + dev->tx_goingon = 1; usb_fill_bulk_urb (urb, dev->udev, dev->out, skb->data, skb->len, tx_complete, skb); @@ -972,6 +986,7 @@ static int usbnet_start_xmit (struct sk_buff *skb, struct net_device *net) spin_lock_irqsave (&dev->txq.lock, flags); + usb_mark_last_busy(dev->udev); switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { case -EPIPE: netif_stop_queue (net); @@ -1005,6 +1020,28 @@ drop: return retval; } +static int usbnet_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int retval; + unsigned long flags; + + spin_lock_irqsave(&dev->txq.lock, flags); + if (dev->suspend_count) { + netif_stop_queue(net); + dev->tx_skb = skb; + if (!schedule_work (&dev->waker)) + deverr(dev, "waker may have been dropped"); + else + devdbg(dev, "waker scheduled"); + spin_unlock_irqrestore(&dev->txq.lock, flags); + return NET_XMIT_SUCCESS; + } + spin_unlock_irqrestore(&dev->txq.lock, flags); + + retval = __usbnet_start_xmit(skb, net); + return retval; +} /*-------------------------------------------------------------------------*/ @@ -1024,6 +1061,8 @@ static void usbnet_bh (unsigned long param) rx_process (dev, skb); continue; case tx_done: + dev->tx_goingon = 0; + /* fall through */ case rx_cleanup: usb_free_urb (entry->urb); dev_kfree_skb (skb); @@ -1043,6 +1082,7 @@ static void usbnet_bh (unsigned long param) } else if (netif_running (dev->net) && netif_device_present (dev->net) && !timer_pending (&dev->delay) + && !dev->suspend_count && !test_bit (EVENT_RX_HALT, &dev->flags)) { int temp = dev->rxq.qlen; int qlen = RX_QLEN (dev); @@ -1161,6 +1201,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) dev->bh.func = usbnet_bh; dev->bh.data = (unsigned long) dev; INIT_WORK (&dev->kevent, kevent); + INIT_WORK (&dev->waker, waker); dev->delay.function = usbnet_bh; dev->delay.data = (unsigned long) dev; init_timer (&dev->delay); @@ -1269,24 +1313,66 @@ EXPORT_SYMBOL_GPL(usbnet_probe); * resume only when the last interface is resumed */ +static void waker(struct work_struct *work) +{ + struct usbnet *dev = container_of(work, struct usbnet, waker); + + if (!usb_autopm_get_interface(dev->intf)) { + usb_autopm_put_interface(dev->intf); + } else { + devdbg(dev, "autoresume failed"); + } +} + +static void stop_traffic(struct usbnet *dev) +{ + int temp; + DECLARE_WAIT_QUEUE_HEAD_ONSTACK (unlink_wakeup); + DECLARE_WAITQUEUE (wait, current); + + /* ensure there are no more active urbs */ + add_wait_queue (&unlink_wakeup, &wait); + dev->wait = &unlink_wakeup; + temp = unlink_urbs (dev, &dev->txq) + unlink_urbs (dev, &dev->rxq); + + /* maybe wait for deletions to finish. */ + while (!skb_queue_empty(&dev->rxq) + && !skb_queue_empty(&dev->txq) + && !skb_queue_empty(&dev->done)) { + msleep(UNLINK_TIMEOUT_MS); + if (netif_msg_ifdown (dev)) + devdbg (dev, "waited for %d urb completions", temp); + } + dev->wait = NULL; + remove_wait_queue (&unlink_wakeup, &wait); + + usb_kill_urb(dev->interrupt); +} + int usbnet_suspend (struct usb_interface *intf, pm_message_t message) { - struct usbnet *dev = usb_get_intfdata(intf); + struct usbnet *dev = usb_get_intfdata(intf); - if (!dev->suspend_count++) { - /* - * accelerate emptying of the rx and queues, to avoid - * having everything error out. - */ - netif_device_detach (dev->net); - (void) unlink_urbs (dev, &dev->rxq); - (void) unlink_urbs (dev, &dev->txq); - /* - * reattach so runtime management can use and - * wake the device - */ - netif_device_attach (dev->net); + devdbg(dev, "%s: begin", __FUNCTION__); + + if (dev->suspend_count++) + return 0; + + /* check for ongoing tx traffic */ + if (dev->tx_goingon && dev->udev->auto_pm) { + dev->suspend_count--; + return -EBUSY; } + + stop_traffic(dev); + + /* cancel work */ + dev->flags = 0; + del_timer_sync(&dev->delay); + cancel_work_sync(&dev->kevent); + + devdbg(dev, "%s: end", __FUNCTION__); + return 0; } EXPORT_SYMBOL_GPL(usbnet_suspend); @@ -1294,9 +1380,32 @@ EXPORT_SYMBOL_GPL(usbnet_suspend); int usbnet_resume (struct usb_interface *intf) { struct usbnet *dev = usb_get_intfdata(intf); + int status; + + devdbg(dev, "%s: begin", __FUNCTION__); + + if (--dev->suspend_count) + return 0; + + status = init_status (dev, dev->intf); + if (dev->interrupt) { + status = usb_submit_urb (dev->interrupt, GFP_KERNEL); + if (status < 0) { + devdbg(dev, "failed restarting interrupt urb"); + } + } + + tasklet_schedule(&dev->bh); + + /* transmit package that triggered resume */ + if (dev->tx_skb) { + status = __usbnet_start_xmit(dev->tx_skb, dev->net); + dev->tx_skb = NULL; + } + + netif_wake_queue(dev->net); - if (!--dev->suspend_count) - tasklet_schedule (&dev->bh); + devdbg(dev, "%s: end", __FUNCTION__); return 0; } diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index ba09fe8..93f3625 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -64,6 +64,12 @@ struct usbnet { # define EVENT_RX_MEMORY 2 # define EVENT_STS_SPLIT 3 # define EVENT_LINK_RESET 4 + + /* autosuspend helpers */ + struct work_struct waker; + int used; + int tx_goingon; + struct sk_buff *tx_skb; /* skb queued during suspend */ }; static inline struct usb_driver *driver_of(struct usb_interface *intf)