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.proj@xxxxxxxxx> Cc: David S. Miller <davem@xxxxxxxxxxxxx> Cc: Stephen Hemminger <shemminger@xxxxxxxxxx> Cc: Felipe Balbi <balbi@xxxxxx> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> Cc: Manu Gautam <mgautam@xxxxxxxxxxxxxx> Cc: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> --- 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. Changes for v4: - Move NAPI disable to a work queue and see if this won't cause KE on replug. drivers/usb/gadget/u_ether.c | 138 ++++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index 97b0277..785d018 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,8 @@ struct eth_dev { struct sk_buff_head *list); struct work_struct work; + struct work_struct napi_work; + struct napi_struct rx_napi; unsigned long todo; #define WORK_RX_MEMORY 0 @@ -187,6 +191,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 +247,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 +257,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 +290,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 +314,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 +345,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 +405,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 +432,51 @@ 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 napi_disable_work(struct work_struct *work) +{ + struct eth_dev *dev = container_of(work, struct eth_dev, work); + + napi_disable(&dev->rx_napi); +} + static void eth_work(struct work_struct *work) { struct eth_dev *dev = container_of(work, struct eth_dev, work); @@ -564,7 +608,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 +668,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 +695,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,9 +813,11 @@ 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); + INIT_WORK(&dev->napi_work, napi_disable_work); INIT_LIST_HEAD(&dev->tx_reqs); INIT_LIST_HEAD(&dev->rx_reqs); @@ -830,6 +877,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 +1161,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 +1169,9 @@ void gether_disconnect(struct gether *link) DBG(dev, "%s\n", __func__); + if (!schedule_work(&dev->napi_work)) + ERROR(dev, "NAPI disable work cannot be scheduled\n"); + netif_tx_lock(dev->net); netif_stop_queue(dev->net); netif_tx_unlock(dev->net); @@ -1142,6 +1194,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.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html