usbnet: add rx queue pausing
authorJussi Kivilinna <jussi.kivilinna@mbnet.fi>
Tue, 11 Aug 2009 19:57:16 +0000 (22:57 +0300)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 14 Aug 2009 13:14:04 +0000 (09:14 -0400)
Add rx queue pausing to usbnet. This is needed by rndis_wlan so that it can
control rx queue and prevent received packets from being send forward before
rndis_wlan receives and handles 'media connect'-indication. Without this
establishing WPA connections is hard and fail often.

[v2] - removed unneeded use of skb_clone

Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/usb/usbnet.c
drivers/net/wireless/rndis_wlan.c
include/linux/usb/usbnet.h

index af1fe4696509dfc46e355d5e335d8f28316b1849..7d471fca27434ada2269fec7496fbee089d44b99 100644 (file)
@@ -233,6 +233,11 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb)
 {
        int     status;
 
+       if (test_bit(EVENT_RX_PAUSED, &dev->flags)) {
+               skb_queue_tail(&dev->rxq_pause, skb);
+               return;
+       }
+
        skb->protocol = eth_type_trans (skb, dev->net);
        dev->net->stats.rx_packets++;
        dev->net->stats.rx_bytes += skb->len;
@@ -525,6 +530,41 @@ static void intr_complete (struct urb *urb)
                deverr(dev, "intr resubmit --> %d", status);
 }
 
+/*-------------------------------------------------------------------------*/
+void usbnet_pause_rx(struct usbnet *dev)
+{
+       set_bit(EVENT_RX_PAUSED, &dev->flags);
+
+       if (netif_msg_rx_status(dev))
+               devdbg(dev, "paused rx queue enabled");
+}
+EXPORT_SYMBOL_GPL(usbnet_pause_rx);
+
+void usbnet_resume_rx(struct usbnet *dev)
+{
+       struct sk_buff *skb;
+       int num = 0;
+
+       clear_bit(EVENT_RX_PAUSED, &dev->flags);
+
+       while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) {
+               usbnet_skb_return(dev, skb);
+               num++;
+       }
+
+       tasklet_schedule(&dev->bh);
+
+       if (netif_msg_rx_status(dev))
+               devdbg(dev, "paused rx queue disabled, %d skbs requeued", num);
+}
+EXPORT_SYMBOL_GPL(usbnet_resume_rx);
+
+void usbnet_purge_paused_rxq(struct usbnet *dev)
+{
+       skb_queue_purge(&dev->rxq_pause);
+}
+EXPORT_SYMBOL_GPL(usbnet_purge_paused_rxq);
+
 /*-------------------------------------------------------------------------*/
 
 // unlink pending rx/tx; completion handlers do all other cleanup
@@ -623,6 +663,8 @@ int usbnet_stop (struct net_device *net)
 
        usb_kill_urb(dev->interrupt);
 
+       usbnet_purge_paused_rxq(dev);
+
        /* deferred work (task, timer, softirq) must also stop.
         * can't flush_scheduled_work() until we drop rtnl (later),
         * else workers could deadlock; so make workers a NOP.
@@ -1113,7 +1155,6 @@ static void usbnet_bh (unsigned long param)
 }
 
 
-\f
 /*-------------------------------------------------------------------------
  *
  * USB Device Driver support
@@ -1210,6 +1251,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
        skb_queue_head_init (&dev->rxq);
        skb_queue_head_init (&dev->txq);
        skb_queue_head_init (&dev->done);
+       skb_queue_head_init(&dev->rxq_pause);
        dev->bh.func = usbnet_bh;
        dev->bh.data = (unsigned long) dev;
        INIT_WORK (&dev->kevent, kevent);
index 828dc1825bba1da9afa3532a76330bb230410302..d42692dfbc676731384f5a731b8e1fd7e7d208f4 100644 (file)
@@ -1764,8 +1764,15 @@ static int rndis_iw_set_essid(struct net_device *dev,
 
        if (!wrqu->essid.flags || length == 0)
                return disassociate(usbdev, 1);
-       else
+       else {
+               /* Pause and purge rx queue, so we don't pass packets before
+                * 'media connect'-indication.
+                */
+               usbnet_pause_rx(usbdev);
+               usbnet_purge_paused_rxq(usbdev);
+
                return set_essid(usbdev, &ssid);
+       }
 }
 
 
@@ -2328,6 +2335,8 @@ get_bssid:
                        memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN);
                        wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
                }
+
+               usbnet_resume_rx(usbdev);
        }
 
        if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending)) {
@@ -2541,6 +2550,8 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)
 
        switch (msg->status) {
        case RNDIS_STATUS_MEDIA_CONNECT:
+               usbnet_pause_rx(usbdev);
+
                devinfo(usbdev, "media connect");
 
                /* queue work to avoid recursive calls into rndis_command */
index de8b4b18961b35e154f33f2362bb4a319c9d1145..09514252d84ec2f7ca894bf6a16c7e6be31967f4 100644 (file)
@@ -53,6 +53,7 @@ struct usbnet {
        struct sk_buff_head     rxq;
        struct sk_buff_head     txq;
        struct sk_buff_head     done;
+       struct sk_buff_head     rxq_pause;
        struct urb              *interrupt;
        struct tasklet_struct   bh;
 
@@ -63,6 +64,7 @@ struct usbnet {
 #              define EVENT_RX_MEMORY  2
 #              define EVENT_STS_SPLIT  3
 #              define EVENT_LINK_RESET 4
+#              define EVENT_RX_PAUSED  5
 };
 
 static inline struct usb_driver *driver_of(struct usb_interface *intf)
@@ -190,6 +192,10 @@ extern void usbnet_defer_kevent (struct usbnet *, int);
 extern void usbnet_skb_return (struct usbnet *, struct sk_buff *);
 extern void usbnet_unlink_rx_urbs(struct usbnet *);
 
+extern void usbnet_pause_rx(struct usbnet *);
+extern void usbnet_resume_rx(struct usbnet *);
+extern void usbnet_purge_paused_rxq(struct usbnet *);
+
 extern int usbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd);
 extern int usbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd);
 extern u32 usbnet_get_link (struct net_device *net);