usb: gadget: net2280: fix infinite loop in irq handler
authorJussi Kivilinna <jussi.kivilinna@haltian.com>
Fri, 12 Aug 2016 14:29:34 +0000 (17:29 +0300)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Thu, 25 Aug 2016 09:13:10 +0000 (12:13 +0300)
With SuperSpeed CDC NCM gadget, net2280 would get stuck in
'handle_ep_small' function. Triggering issue requires large
TCP transfer from host to USB3380.

Patch adds check for stuck condition and prevents hard lockup.

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@haltian.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/gadget/udc/net2280.c

index 614ab95..cd876bf 100644 (file)
@@ -1137,8 +1137,10 @@ dma_done(struct net2280_ep *ep,  struct net2280_request *req, u32 dmacount,
        done(ep, req, status);
 }
 
-static void scan_dma_completions(struct net2280_ep *ep)
+static int scan_dma_completions(struct net2280_ep *ep)
 {
+       int num_completed = 0;
+
        /* only look at descriptors that were "naturally" retired,
         * so fifo and list head state won't matter
         */
@@ -1166,6 +1168,7 @@ static void scan_dma_completions(struct net2280_ep *ep)
                                break;
                        /* single transfer mode */
                        dma_done(ep, req, tmp, 0);
+                       num_completed++;
                        break;
                } else if (!ep->is_in &&
                           (req->req.length % ep->ep.maxpacket) &&
@@ -1194,7 +1197,10 @@ static void scan_dma_completions(struct net2280_ep *ep)
                        }
                }
                dma_done(ep, req, tmp, 0);
+               num_completed++;
        }
+
+       return num_completed;
 }
 
 static void restart_dma(struct net2280_ep *ep)
@@ -2547,8 +2553,11 @@ static void handle_ep_small(struct net2280_ep *ep)
        /* manual DMA queue advance after short OUT */
        if (likely(ep->dma)) {
                if (t & BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT)) {
-                       u32     count;
+                       struct net2280_request *stuck_req = NULL;
                        int     stopped = ep->stopped;
+                       int     num_completed;
+                       int     stuck = 0;
+                       u32     count;
 
                        /* TRANSFERRED works around OUT_DONE erratum 0112.
                         * we expect (N <= maxpacket) bytes; host wrote M.
@@ -2560,7 +2569,7 @@ static void handle_ep_small(struct net2280_ep *ep)
                                /* any preceding dma transfers must finish.
                                 * dma handles (M >= N), may empty the queue
                                 */
-                               scan_dma_completions(ep);
+                               num_completed = scan_dma_completions(ep);
                                if (unlikely(list_empty(&ep->queue) ||
                                                ep->out_overflow)) {
                                        req = NULL;
@@ -2580,6 +2589,31 @@ static void handle_ep_small(struct net2280_ep *ep)
                                                req = NULL;
                                        break;
                                }
+
+                               /* Escape loop if no dma transfers completed
+                                * after few retries.
+                                */
+                               if (num_completed == 0) {
+                                       if (stuck_req == req &&
+                                           readl(&ep->dma->dmadesc) !=
+                                                 req->td_dma && stuck++ > 5) {
+                                               count = readl(
+                                                       &ep->dma->dmacount);
+                                               count &= DMA_BYTE_COUNT_MASK;
+                                               req = NULL;
+                                               ep_dbg(ep->dev, "%s escape stuck %d, count %u\n",
+                                                       ep->ep.name, stuck,
+                                                       count);
+                                               break;
+                                       } else if (stuck_req != req) {
+                                               stuck_req = req;
+                                               stuck = 0;
+                                       }
+                               } else {
+                                       stuck_req = NULL;
+                                       stuck = 0;
+                               }
+
                                udelay(1);
                        }