In order to reduce the interrupt times in the embedded system,
a receiving workqueue is introduced.
This modification also enhanced the overall throughput as the
benefits of reducing interrupt occurrence.

This work was derived from previous work:
u_ether: move hardware transmit to RX workqueue.
Which should be base on codeaurora's work.

However, the benchmark on my platform shows the throughput
with workqueue is slightly better than NAPI.

Signed-off-by: Weinn Jheng <clanlab.p...@gmail.com>
Cc: David S. Miller <da...@davemloft.net>
Cc: Stephen Hemminger <shemmin...@vyatta.com>
Cc: Felipe Balbi <ba...@ti.com>
Cc: Greg Kroah-Hartman <gre...@linuxfoundation.org>
Cc: Manu Gautam <mgau...@codeaurora.org>
Cc: Andrzej Pietrasiewicz <andrze...@samsung.com>
---
Changes for v2:
  - Trying to fix the plug/unplug problem which cause KE.
Changes for v3:
  - Fix plug/unplug problem in gether_disconenct.
  - Refine the rx/tx_completion function assigment.
  - This patch has been tested on beaglebone black.

 drivers/usb/gadget/u_ether.c | 127 +++++++++++++++++++++++++++++--------------
 1 file changed, 87 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c
index b7d4f82..97007e9 100644
--- a/drivers/usb/gadget/u_ether.c
+++ b/drivers/usb/gadget/u_ether.c
@@ -48,6 +48,8 @@
 
 #define UETH__VERSION  "29-May-2008"
 
+#define GETHER_NAPI_WEIGHT     5
+
 struct eth_dev {
        /* lock is held while accessing port_usb
         */
@@ -72,6 +74,7 @@ struct eth_dev {
                                                struct sk_buff_head *list);
 
        struct work_struct      work;
+       struct napi_struct      rx_napi;
 
        unsigned long           todo;
 #define        WORK_RX_MEMORY          0
@@ -187,6 +190,7 @@ static void defer_kevent(struct eth_dev *dev, int flag)
 }
 
 static void rx_complete(struct usb_ep *ep, struct usb_request *req);
+static void tx_complete(struct usb_ep *ep, struct usb_request *req);
 
 static int
 rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
@@ -242,7 +246,6 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, 
gfp_t gfp_flags)
 
        req->buf = skb->data;
        req->length = size;
-       req->complete = rx_complete;
        req->context = skb;
 
        retval = usb_ep_queue(out, req, gfp_flags);
@@ -253,18 +256,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            rx_queue = 0;
 
        switch (status) {
 
@@ -288,30 +289,8 @@ 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)
+                       rx_queue = 1;
                break;
 
        /* software-driven interface shutdown */
@@ -334,28 +313,27 @@ quiesce:
                /* FALLTHROUGH */
 
        default:
+               rx_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 (rx_queue && likely(napi_schedule_prep(&dev->rx_napi)))
+               __napi_schedule(&dev->rx_napi);
 }
 
 static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
 {
        unsigned                i;
        struct usb_request      *req;
+       bool                    usb_in;
 
        if (!n)
                return -ENOMEM;
@@ -366,10 +344,22 @@ static int prealloc(struct list_head *list, struct usb_ep 
*ep, unsigned n)
                if (i-- == 0)
                        goto extra;
        }
+
+       if (ep->desc->bEndpointAddress & USB_DIR_IN)
+               usb_in = true;
+       else
+               usb_in = false;
+
        while (i--) {
                req = usb_ep_alloc_request(ep, GFP_ATOMIC);
                if (!req)
                        return list_empty(list) ? -ENOMEM : 0;
+               /* update completion handler */
+               if (usb_in)
+                       req->complete = tx_complete;
+               else
+                       req->complete = rx_complete;
+
                list_add(&req->list, list);
        }
        return 0;
@@ -414,16 +404,24 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
 {
        struct usb_request      *req;
        unsigned long           flags;
+       int                     rx_counts = 0;
 
        /* fill unused rxq slots with some skb */
        spin_lock_irqsave(&dev->req_lock, flags);
        while (!list_empty(&dev->rx_reqs)) {
+
+               if (++rx_counts > qlen(dev->gadget, dev->qmult))
+                       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;
                }
@@ -433,6 +431,44 @@ static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
        spin_unlock_irqrestore(&dev->req_lock, flags);
 }
 
+static int gether_poll(struct napi_struct *napi, int budget)
+{
+       struct eth_dev  *dev = container_of(napi, struct eth_dev, rx_napi);
+       struct sk_buff  *skb;
+       unsigned int    work_done = 0;
+       int             status = 0;
+
+       if (!dev->port_usb)
+               return work_done;
+
+       while ((skb = skb_dequeue(&dev->rx_frames))) {
+               if (status < 0
+                               || ETH_HLEN > skb->len
+                               || skb->len > VLAN_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);
+               work_done++;
+       }
+
+       if (work_done < budget)
+               napi_complete(&dev->rx_napi);
+
+       /* put rx_fill here for avoiding running out rx_reqs */
+       if (netif_running(dev->net))
+               rx_fill(dev, GFP_KERNEL);
+
+       return work_done;
+}
+
 static void eth_work(struct work_struct *work)
 {
        struct eth_dev  *dev = container_of(work, struct eth_dev, work);
@@ -564,7 +600,6 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
        }
        req->buf = skb->data;
        req->context = skb;
-       req->complete = tx_complete;
 
        /* NCM requires no zlp if transfer is dwNtbInMaxSize */
        if (dev->port_usb->is_fixed &&
@@ -625,6 +660,7 @@ static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
        /* and open the tx floodgates */
        atomic_set(&dev->tx_qlen, 0);
        netif_wake_queue(dev->net);
+       napi_enable(&dev->rx_napi);
 }
 
 static int eth_open(struct net_device *net)
@@ -651,6 +687,7 @@ static int eth_stop(struct net_device *net)
        unsigned long   flags;
 
        VDBG(dev, "%s\n", __func__);
+       napi_disable(&dev->rx_napi);
        netif_stop_queue(net);
 
        DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n",
@@ -768,6 +805,7 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g,
                return ERR_PTR(-ENOMEM);
 
        dev = netdev_priv(net);
+       netif_napi_add(net, &dev->rx_napi, gether_poll, GETHER_NAPI_WEIGHT);
        spin_lock_init(&dev->lock);
        spin_lock_init(&dev->req_lock);
        INIT_WORK(&dev->work, eth_work);
@@ -830,6 +868,7 @@ struct net_device *gether_setup_name_default(const char 
*netname)
                return ERR_PTR(-ENOMEM);
 
        dev = netdev_priv(net);
+       netif_napi_add(net, &dev->rx_napi, gether_poll, GETHER_NAPI_WEIGHT);
        spin_lock_init(&dev->lock);
        spin_lock_init(&dev->req_lock);
        INIT_WORK(&dev->work, eth_work);
@@ -1113,6 +1152,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)
@@ -1120,6 +1160,7 @@ void gether_disconnect(struct gether *link)
 
        DBG(dev, "%s\n", __func__);
 
+       napi_disable(&dev->rx_napi);
        netif_stop_queue(dev->net);
        netif_carrier_off(dev->net);
 
@@ -1139,6 +1180,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->in_ep->driver_data = NULL;
        link->in_ep->desc = NULL;
 
-- 
1.7.11.2.138.g2b53359

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to