usb: gadget: ether: fix clog tx transmit
Jay Cheng [Sun, 30 Sep 2012 02:07:22 +0000 (22:07 -0400)]
With g_len initial from 0 and buffer size is 10, it cause interrupt(IOC)
is triggered at entry 0 and 5, which is 1st and 6th entry in list. This deviate
double bufferring design that expect to trigger interrupt at half and full.
If interrupt is trggered at 0 and 5, when upper layer pass through data faster
then u_ether driver can free up, then the data flow start clogging from g_len=6
till g_len=9 and no interrupt is triggered to free up memory to consume data
from upper layer as illustrated below.

[0][1][2][3][4] [5][6][7][8][9]
IOC             IOC

Initial g_len as 1, so interrupt will be triggered at 5th and 10th as illustrated
below. Especially, 10th entry, which is also last entry, must set interrupt to
free up memory.

[0][1][2][3][4] [5][6][7][8][9]
            IOC             IOC

Also enforce last entry in list to trigger interrupt in case user space
change qmult value at run time that will cause misalignment with buffer size.

For example:
echo 4 > /sys/module/g_android/parameters/qmult

[0][1][2][3][4] [5][6][7][8][9]
         IOC          IOC   IOC

bug 1054552
Reviewed-on: http://git-master/r/139969
(cherry picked from commit 4ea9a0eff24d5f3c44402732e35bf12eaed7ed79)

Change-Id: I449a007036f6aea73626921bb24925b2ec57e167
Signed-off-by: Jay Cheng <jacheng@nvidia.com>
Reviewed-on: http://git-master/r/145243
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Venkat Moganty <vmoganty@nvidia.com>
Reviewed-by: Michael Hsu <mhsu@nvidia.com>

drivers/usb/gadget/u_ether.c

index dd2615a..4056b9b 100644 (file)
@@ -496,6 +496,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
        unsigned long           flags;
        struct usb_ep           *in;
        u16                     cdc_filter;
+       int                     buffer_is_full = 0;
 
        spin_lock_irqsave(&dev->lock, flags);
        if (dev->port_usb) {
@@ -549,8 +550,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
        list_del(&req->list);
 
        /* temporarily stop TX queue when the freelist empties */
-       if (list_empty(&dev->tx_reqs))
+       if (list_empty(&dev->tx_reqs)) {
+               buffer_is_full = 1;
                netif_stop_queue(net);
+       }
        spin_unlock_irqrestore(&dev->req_lock, flags);
 
        /* no buffer copies needed, unless the network stack did it
@@ -590,13 +593,19 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
 
        req->length = length;
 
+       /* In case user change qmult after f_rndis is enabled, qmult might be
+        * misaligned with buffer size. Always enable interrupt when buffer
+        * is full
+        */
+       if (buffer_is_full)
+               req->no_interrupt = 0;
+
        /* throttle high/super speed IRQ rate back slightly */
-       if (gadget_is_dualspeed(dev->gadget))
+       else if (gadget_is_dualspeed(dev->gadget))
                req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH ||
                                     dev->gadget->speed == USB_SPEED_SUPER)
                        ? ((atomic_read(&dev->tx_qlen) % qmult) != 0)
                        : 0;
-
        retval = usb_ep_queue(in, req, GFP_ATOMIC);
        switch (retval) {
        default:
@@ -630,7 +639,7 @@ static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
        rx_fill(dev, gfp_flags);
 
        /* and open the tx floodgates */
-       atomic_set(&dev->tx_qlen, 0);
+       atomic_set(&dev->tx_qlen, 1);
        netif_wake_queue(dev->net);
 }