usb: u_ether: Add workqueue as bottom half handler for rx data path
authorBadhri Jagan Sridharan <Badhri@google.com>
Thu, 25 Sep 2014 01:58:23 +0000 (18:58 -0700)
committerBadhri Jagan Sridharan <Badhri@google.com>
Thu, 25 Sep 2014 01:59:22 +0000 (18:59 -0700)
u_ether driver passes rx data to network layer and resubmits the
request back to usb hardware in interrupt context. Network layer
processes rx data by scheduling tasklet. For high throughput
scenarios on rx data path driver is spending lot of time in interrupt
context due to rx data processing by tasklet and continuous completion
and re-submission of the usb requests which results in watchdog bark.
Hence move the rx data processing and usb request submission to a
workqueue bottom half handler.

Change-Id: I316de8e267997137ac189a8b7b2846fa325f4a5a
Signed-off-by: Badhri Jagan Sridharan <Badhri@google.com>
drivers/usb/gadget/u_ether.c

index 801e326e54c0542c5dea38ac7785c44493b44eff..734d64b8e2c100d20aaf134ff066265e03c28f46 100644 (file)
@@ -48,6 +48,8 @@
 
 #define UETH__VERSION  "29-May-2008"
 
+static struct workqueue_struct *uether_wq;
+
 struct eth_dev {
        /* lock is held while accessing port_usb
         */
@@ -71,6 +73,7 @@ struct eth_dev {
                                                struct sk_buff_head *list);
 
        struct work_struct      work;
+       struct work_struct      rx_work;
 
        unsigned long           todo;
 #define        WORK_RX_MEMORY          0
@@ -259,18 +262,16 @@ enomem:
                DBG(dev, "rx submit --> %d\n", retval);
                if (skb)
                        dev_kfree_skb_any(skb);
-               spin_lock_irqsave(&dev->req_lock, flags);
-               list_add(&req->list, &dev->rx_reqs);
-               spin_unlock_irqrestore(&dev->req_lock, flags);
        }
        return retval;
 }
 
 static void rx_complete(struct usb_ep *ep, struct usb_request *req)
 {
-       struct sk_buff  *skb = req->context, *skb2;
+       struct sk_buff  *skb = req->context;
        struct eth_dev  *dev = ep->driver_data;
        int             status = req->status;
+       bool            queue = 0;
 
        switch (status) {
 
@@ -294,30 +295,9 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req)
                } else {
                        skb_queue_tail(&dev->rx_frames, skb);
                }
-               skb = NULL;
-
-               skb2 = skb_dequeue(&dev->rx_frames);
-               while (skb2) {
-                       if (status < 0
-                                       || ETH_HLEN > skb2->len
-                                       || skb2->len > VLAN_ETH_FRAME_LEN) {
-                               dev->net->stats.rx_errors++;
-                               dev->net->stats.rx_length_errors++;
-                               DBG(dev, "rx length %d\n", skb2->len);
-                               dev_kfree_skb_any(skb2);
-                               goto next_frame;
-                       }
-                       skb2->protocol = eth_type_trans(skb2, dev->net);
-                       dev->net->stats.rx_packets++;
-                       dev->net->stats.rx_bytes += skb2->len;
 
-                       /* no buffer copies needed, unless hardware can't
-                        * use skb buffers.
-                        */
-                       status = netif_rx(skb2);
-next_frame:
-                       skb2 = skb_dequeue(&dev->rx_frames);
-               }
+               if (!status)
+                       queue = 1;
                break;
 
        /* software-driven interface shutdown */
@@ -340,22 +320,20 @@ quiesce:
                /* FALLTHROUGH */
 
        default:
+               queue = 1;
+               dev_kfree_skb_any(skb);
                dev->net->stats.rx_errors++;
                DBG(dev, "rx status %d\n", status);
                break;
        }
 
-       if (skb)
-               dev_kfree_skb_any(skb);
-       if (!netif_running(dev->net)) {
 clean:
-               spin_lock(&dev->req_lock);
-               list_add(&req->list, &dev->rx_reqs);
-               spin_unlock(&dev->req_lock);
-               req = NULL;
-       }
-       if (req)
-               rx_submit(dev, req, GFP_ATOMIC);
+       spin_lock(&dev->req_lock);
+       list_add(&req->list, &dev->rx_reqs);
+       spin_unlock(&dev->req_lock);
+
+       if (queue)
+               queue_work(uether_wq, &dev->rx_work);
 }
 
 static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
@@ -420,16 +398,24 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
 {
        struct usb_request      *req;
        unsigned long           flags;
+       int                     req_cnt = 0;
 
        /* fill unused rxq slots with some skb */
        spin_lock_irqsave(&dev->req_lock, flags);
        while (!list_empty(&dev->rx_reqs)) {
+               /* break the nexus of continuous completion and re-submission*/
+               if (++req_cnt > qlen(dev->gadget))
+                       break;
+
                req = container_of(dev->rx_reqs.next,
                                struct usb_request, list);
                list_del_init(&req->list);
                spin_unlock_irqrestore(&dev->req_lock, flags);
 
                if (rx_submit(dev, req, gfp_flags) < 0) {
+                       spin_lock_irqsave(&dev->req_lock, flags);
+                       list_add(&req->list, &dev->rx_reqs);
+                       spin_unlock_irqrestore(&dev->req_lock, flags);
                        defer_kevent(dev, WORK_RX_MEMORY);
                        return;
                }
@@ -439,6 +425,36 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
        spin_unlock_irqrestore(&dev->req_lock, flags);
 }
 
+static void process_rx_w(struct work_struct *work)
+{
+       struct eth_dev  *dev = container_of(work, struct eth_dev, rx_work);
+       struct sk_buff  *skb;
+       int             status = 0;
+
+       if (!dev->port_usb)
+               return;
+
+       while ((skb = skb_dequeue(&dev->rx_frames))) {
+               if (status < 0
+                               || ETH_HLEN > skb->len
+                               || skb->len > ETH_FRAME_LEN) {
+                       dev->net->stats.rx_errors++;
+                       dev->net->stats.rx_length_errors++;
+                       DBG(dev, "rx length %d\n", skb->len);
+                       dev_kfree_skb_any(skb);
+                       continue;
+               }
+               skb->protocol = eth_type_trans(skb, dev->net);
+               dev->net->stats.rx_packets++;
+               dev->net->stats.rx_bytes += skb->len;
+
+               status = netif_rx_ni(skb);
+       }
+
+       if (netif_running(dev->net))
+               rx_fill(dev, GFP_KERNEL);
+}
+
 static void eth_work(struct work_struct *work)
 {
        struct eth_dev  *dev = container_of(work, struct eth_dev, work);
@@ -775,6 +791,7 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
        spin_lock_init(&dev->lock);
        spin_lock_init(&dev->req_lock);
        INIT_WORK(&dev->work, eth_work);
+       INIT_WORK(&dev->rx_work, process_rx_w);
        INIT_LIST_HEAD(&dev->tx_reqs);
        INIT_LIST_HEAD(&dev->rx_reqs);
 
@@ -933,6 +950,7 @@ void gether_disconnect(struct gether *link)
 {
        struct eth_dev          *dev = link->ioport;
        struct usb_request      *req;
+       struct sk_buff          *skb;
 
        WARN_ON(!dev);
        if (!dev)
@@ -974,6 +992,12 @@ void gether_disconnect(struct gether *link)
                spin_lock(&dev->req_lock);
        }
        spin_unlock(&dev->req_lock);
+
+       spin_lock(&dev->rx_frames.lock);
+       while ((skb = __skb_dequeue(&dev->rx_frames)))
+               dev_kfree_skb_any(skb);
+       spin_unlock(&dev->rx_frames.lock);
+
        link->out_ep->driver_data = NULL;
        link->out_ep->desc = NULL;
 
@@ -986,3 +1010,23 @@ void gether_disconnect(struct gether *link)
        dev->port_usb = NULL;
        spin_unlock(&dev->lock);
 }
+
+static int __init gether_init(void)
+{
+       uether_wq  = create_singlethread_workqueue("uether");
+       if (!uether_wq) {
+               pr_err("%s: Unable to create workqueue: uether\n", __func__);
+               return -ENOMEM;
+       }
+       return 0;
+}
+module_init(gether_init);
+
+static void __exit gether_exit(void)
+{
+       destroy_workqueue(uether_wq);
+
+}
+module_exit(gether_exit);
+MODULE_DESCRIPTION("ethernet over USB driver");
+MODULE_LICENSE("GPL v2");