ChangeSet 1.1325.4.10, 2003/09/23 17:18:03-07:00, david-b@pacbell.net [PATCH] USB: usb "ether" net gadget Minor updates: - Reduce memory utilization in two ways: * Dynamically, by pre-allocating all the usb_request objects that will be used; if the pre-allocated ones are in use, then tx will throttle down. This behaves better under heavy load. * Statically, by pre-allocating fewer such requests in the typical "no DMA queueing" case ... the best we can do is make sure that when the next completion IRQ fires, the controller already has a transfer ready. Having queues deeper than two elements only helps if the CPU doesn't need to start each transfer by hand (as with net2280). - Diagnostics look more like other network driver diagnostics; they use the network interface name. drivers/usb/gadget/ether.c | 241 +++++++++++++++++++++++++++++++-------------- 1 files changed, 170 insertions(+), 71 deletions(-) diff -Nru a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c --- a/drivers/usb/gadget/ether.c Thu Sep 25 14:31:50 2003 +++ b/drivers/usb/gadget/ether.c Thu Sep 25 14:31:50 2003 @@ -93,6 +93,7 @@ struct usb_ep *in_ep, *out_ep, *status_ep; const struct usb_endpoint_descriptor *in, *out, *status; + struct list_head tx_reqs, rx_reqs; struct net_device *net; struct net_device_stats stats; @@ -105,28 +106,6 @@ /*-------------------------------------------------------------------------*/ -/* This driver keeps a variable number of requests queued, more at - * high speeds. (Numbers are just educated guesses, untuned.) - * Shrink the queue if memory is tight, or make it bigger to - * handle bigger traffic bursts between IRQs. - */ - -static unsigned qmult = 4; - -#define HS_FACTOR 5 - -#define qlen(gadget) \ - (qmult*((gadget->speed == USB_SPEED_HIGH) ? HS_FACTOR : 1)) - -/* defer IRQs on highspeed TX */ -#define TX_DELAY 8 - - -module_param (qmult, uint, S_IRUGO|S_IWUSR); - - -/*-------------------------------------------------------------------------*/ - /* Thanks to NetChip Technologies for donating this product ID. * * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! @@ -175,6 +154,7 @@ */ #ifdef CONFIG_USB_ETH_NET2280 #define CHIP "net2280" +#define DEFAULT_QLEN 4 /* has dma chaining */ #define DRIVER_VERSION_NUM 0x0101 #define EP0_MAXPACKET 64 static const char EP_OUT_NAME [] = "ep-a"; @@ -220,7 +200,7 @@ /* supports remote wakeup, but this driver doesn't */ /* no hw optimizations to apply */ -#define hw_optimize(g) do {} while (0); +#define hw_optimize(g) do {} while (0) #endif /* @@ -243,7 +223,7 @@ /* doesn't support remote wakeup? */ /* no hw optimizations to apply */ -#define hw_optimize(g) do {} while (0); +#define hw_optimize(g) do {} while (0) #endif /*-------------------------------------------------------------------------*/ @@ -301,9 +281,32 @@ /*-------------------------------------------------------------------------*/ +#ifndef DEFAULT_QLEN +#define DEFAULT_QLEN 2 /* double buffering by default */ +#endif + +#ifdef HIGHSPEED + +static unsigned qmult = 5; +module_param (qmult, uint, S_IRUGO|S_IWUSR); + + +/* for dual-speed hardware, use deeper queues at highspeed */ +#define qlen(gadget) \ + (DEFAULT_QLEN*((gadget->speed == USB_SPEED_HIGH) ? qmult : 1)) + +/* also defer IRQs on highspeed TX */ +#define TX_DELAY DEFAULT_QLEN + +#else /* !HIGHSPEED ... full speed: */ +#define qlen(gadget) DEFAULT_QLEN +#endif + + +/*-------------------------------------------------------------------------*/ + #define xprintk(d,level,fmt,args...) \ - printk(level "%s %s: " fmt , shortname , (d)->gadget->dev.bus_id , \ - ## args) + printk(level "%s: " fmt , (d)->net->name , ## args) #ifdef DEBUG #undef DEBUG @@ -763,6 +766,7 @@ /*-------------------------------------------------------------------------*/ static void eth_start (struct eth_dev *dev, int gfp_flags); +static int alloc_requests (struct eth_dev *dev, unsigned n, int gfp_flags); static int set_ether_config (struct eth_dev *dev, int gfp_flags) @@ -852,11 +856,21 @@ if (!result && (!dev->in_ep || !dev->out_ep)) result = -ENODEV; + if (result == 0) + result = alloc_requests (dev, qlen (gadget), gfp_flags); + #ifndef DEV_CONFIG_CDC if (result == 0) { netif_carrier_on (dev->net); if (netif_running (dev->net)) eth_start (dev, GFP_ATOMIC); + } else { + (void) usb_ep_disable (dev->in_ep); + dev->in_ep = 0; + dev->in = 0; + (void) usb_ep_disable (dev->out_ep); + dev->out_ep = 0; + dev->out = 0; } #endif /* !CONFIG_CDC_ETHER */ @@ -869,6 +883,8 @@ static void eth_reset_config (struct eth_dev *dev) { + struct usb_request *req; + if (dev->config == 0) return; @@ -877,17 +893,30 @@ netif_stop_queue (dev->net); netif_carrier_off (dev->net); - /* just disable endpoints, forcing completion of pending i/o. - * all our completion handlers free their requests in this case. + /* disable endpoints, forcing (synchronous) completion of + * pending i/o. then free the requests. */ if (dev->in_ep) { usb_ep_disable (dev->in_ep); + while (likely (!list_empty (&dev->tx_reqs))) { + req = container_of (dev->tx_reqs.next, + struct usb_request, list); + list_del (&req->list); + usb_ep_free_request (dev->in_ep, req); + } dev->in_ep = 0; } if (dev->out_ep) { usb_ep_disable (dev->out_ep); + while (likely (!list_empty (&dev->rx_reqs))) { + req = container_of (dev->rx_reqs.next, + struct usb_request, list); + list_del (&req->list); + usb_ep_free_request (dev->out_ep, req); + } dev->out_ep = 0; } + #ifdef EP_STATUS_NUM if (dev->status_ep) { usb_ep_disable (dev->status_ep); @@ -1345,7 +1374,8 @@ static void defer_kevent (struct eth_dev *dev, int flag) { - set_bit (flag, &dev->todo); + if (test_and_set_bit (flag, &dev->todo)) + return; if (!schedule_work (&dev->work)) ERROR (dev, "kevent %d may have been dropped\n", flag); else @@ -1366,7 +1396,7 @@ if ((skb = alloc_skb (size, gfp_flags)) == 0) { DEBUG (dev, "no rx skb\n"); defer_kevent (dev, WORK_RX_MEMORY); - usb_ep_free_request (dev->out_ep, req); + list_add (&req->list, &dev->rx_reqs); return -ENOMEM; } @@ -1381,7 +1411,7 @@ if (retval) { DEBUG (dev, "rx submit --> %d\n", retval); dev_kfree_skb_any (skb); - usb_ep_free_request (dev->out_ep, req); + list_add (&req->list, &dev->rx_reqs); } return retval; } @@ -1421,6 +1451,14 @@ case -ECONNRESET: // unlink case -ESHUTDOWN: // disconnect etc VDEBUG (dev, "rx shutdown, code %d\n", status); + goto quiesce; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: // endpoint reset + DEBUG (dev, "rx %s reset\n", ep->name); + defer_kevent (dev, WORK_RX_MEMORY); +quiesce: + dev_kfree_skb_any (skb); goto clean; /* data overrun */ @@ -1438,28 +1476,96 @@ dev_kfree_skb_any (skb); if (!netif_running (dev->net)) { clean: - usb_ep_free_request (dev->out_ep, req); + list_add (&req->list, &dev->rx_reqs); req = 0; } if (req) rx_submit (dev, req, GFP_ATOMIC); } +static int prealloc (struct list_head *list, struct usb_ep *ep, + unsigned n, int gfp_flags) +{ + unsigned i; + struct usb_request *req; + + if (!n) + return -ENOMEM; + + /* queue/recycle up to N requests */ + i = n; + list_for_each_entry (req, list, list) { + if (i-- == 0) + goto extra; + } + while (i--) { + req = usb_ep_alloc_request (ep, gfp_flags); + if (!req) + return list_empty (list) ? -ENOMEM : 0; + list_add (&req->list, list); + } + return 0; + +extra: + /* free extras */ + for (;;) { + struct list_head *next; + + next = req->list.next; + list_del (&req->list); + usb_ep_free_request (ep, req); + + if (next == list) + break; + + req = container_of (next, struct usb_request, list); + } + return 0; +} + +static int alloc_requests (struct eth_dev *dev, unsigned n, int gfp_flags) +{ + int status; + + status = prealloc (&dev->tx_reqs, dev->in_ep, n, gfp_flags); + if (status < 0) + goto fail; + status = prealloc (&dev->rx_reqs, dev->out_ep, n, gfp_flags); + if (status < 0) + goto fail; + return 0; +fail: + DEBUG (dev, "can't alloc requests\n"); + return status; +} + +static void rx_fill (struct eth_dev *dev, int gfp_flags) +{ + struct usb_request *req; + + clear_bit (WORK_RX_MEMORY, &dev->todo); + + /* fill unused rxq slots with some skb */ + while (!list_empty (&dev->rx_reqs)) { + req = container_of (dev->rx_reqs.next, + struct usb_request, list); + list_del_init (&req->list); + if (rx_submit (dev, req, gfp_flags) < 0) { + defer_kevent (dev, WORK_RX_MEMORY); + return; + } + } +} + static void eth_work (void *_dev) { struct eth_dev *dev = _dev; if (test_bit (WORK_RX_MEMORY, &dev->todo)) { - struct usb_request *req = 0; - if (netif_running (dev->net)) - req = usb_ep_alloc_request (dev->in_ep, GFP_KERNEL); + rx_fill (dev, GFP_KERNEL); else clear_bit (WORK_RX_MEMORY, &dev->todo); - if (req != 0) { - clear_bit (WORK_RX_MEMORY, &dev->todo); - rx_submit (dev, req, GFP_KERNEL); - } } if (dev->todo) @@ -1484,10 +1590,10 @@ } dev->stats.tx_packets++; - usb_ep_free_request (ep, req); + list_add (&req->list, &dev->tx_reqs); dev_kfree_skb_any (skb); - atomic_inc (&dev->tx_qlen); + atomic_dec (&dev->tx_qlen); if (netif_carrier_ok (dev->net)) netif_wake_queue (dev->net); } @@ -1499,10 +1605,10 @@ int retval; struct usb_request *req = 0; - if (!(req = usb_ep_alloc_request (dev->in_ep, GFP_ATOMIC))) { - DEBUG (dev, "no request\n"); - goto drop; - } + req = container_of (dev->tx_reqs.next, struct usb_request, list); + list_del (&req->list); + if (list_empty (&dev->tx_reqs)) + netif_stop_queue (net); /* no buffer copies needed, unless the network stack did it * or the hardware can't use skb buffers. @@ -1537,42 +1643,28 @@ break; case 0: net->trans_start = jiffies; - if (atomic_dec_and_test (&dev->tx_qlen)) - netif_stop_queue (net); + atomic_inc (&dev->tx_qlen); } if (retval) { - DEBUG (dev, "drop, code %d\n", retval); -drop: dev->stats.tx_dropped++; dev_kfree_skb_any (skb); - usb_ep_free_request (dev->in_ep, req); + if (list_empty (&dev->tx_reqs)) + netif_start_queue (net); + list_add (&req->list, &dev->tx_reqs); } return 0; } static void eth_start (struct eth_dev *dev, int gfp_flags) { - struct usb_request *req; - int retval = 0; - unsigned i; - int size = qlen (dev->gadget); - DEBUG (dev, "%s\n", __FUNCTION__); /* fill the rx queue */ - for (i = 0; retval == 0 && i < size; i++) { - req = usb_ep_alloc_request (dev->in_ep, gfp_flags); - if (req) - retval = rx_submit (dev, req, gfp_flags); - else if (i > 0) - defer_kevent (dev, WORK_RX_MEMORY); - else - retval = -ENOMEM; - } + rx_fill (dev, gfp_flags); /* and open the tx floodgates */ - atomic_set (&dev->tx_qlen, size); + atomic_set (&dev->tx_qlen, 0); netif_wake_queue (dev->net); } @@ -1590,7 +1682,7 @@ { struct eth_dev *dev = (struct eth_dev *) net->priv; - DEBUG (dev, "%s\n", __FUNCTION__); + VDEBUG (dev, "%s\n", __FUNCTION__); netif_stop_queue (net); DEBUG (dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", @@ -1604,6 +1696,7 @@ usb_ep_disable (dev->out_ep); if (netif_carrier_ok (dev->net)) { DEBUG (dev, "host still using in/out endpoints\n"); + // FIXME idiom may leave toggle wrong here usb_ep_enable (dev->in_ep, dev->in); usb_ep_enable (dev->out_ep, dev->out); } @@ -1662,6 +1755,8 @@ dev = net->priv; spin_lock_init (&dev->lock); INIT_WORK (&dev->work, eth_work, dev); + INIT_LIST_HEAD (&dev->tx_reqs); + INIT_LIST_HEAD (&dev->rx_reqs); /* network device setup */ dev->net = net; @@ -1714,10 +1809,6 @@ dev->gadget = gadget; set_gadget_data (gadget, dev); gadget->ep0->driver_data = dev; - INFO (dev, "%s, " CHIP ", version: " DRIVER_VERSION "\n", driver_desc); -#ifdef DEV_CONFIG_CDC - INFO (dev, "CDC host enet %s\n", ethaddr); -#endif /* two kinds of host-initiated state changes: * - iff DATA transfer is active, carrier is "on" @@ -1728,8 +1819,16 @@ // SET_NETDEV_DEV (dev->net, &gadget->dev); status = register_netdev (dev->net); - if (status == 0) + if (status == 0) { + + INFO (dev, "%s, " CHIP ", version: " DRIVER_VERSION "\n", + driver_desc); +#ifdef DEV_CONFIG_CDC + INFO (dev, "CDC host enet %s\n", ethaddr); +#endif return status; + } + dev_dbg(&gadget->dev, "register_netdev failed, %d\n", status); fail: eth_unbind (gadget); return status;