Handle all TX errors, not only underruns.
Reinitialize the TX ring after skipping all remaining frames, and
restart the controller when everything has been cleaned up properly.

Original idea from a patch by Havard Skinnemoen.

Signed-off-by: Nicolas Ferre <nicolas.fe...@atmel.com>
---
 drivers/net/ethernet/cadence/macb.c |  124 ++++++++++++++++++++---------------
 1 file changed, 71 insertions(+), 53 deletions(-)

diff --git a/drivers/net/ethernet/cadence/macb.c 
b/drivers/net/ethernet/cadence/macb.c
index 3d3a077..af71151 100644
--- a/drivers/net/ethernet/cadence/macb.c
+++ b/drivers/net/ethernet/cadence/macb.c
@@ -44,6 +44,10 @@
 
 #define MACB_RX_INT_FLAGS      (MACB_BIT(RCOMP) | MACB_BIT(RXUBR)      \
                                 | MACB_BIT(ISR_ROVR))
+#define MACB_TX_INT_FLAGS      (MACB_BIT(ISR_TUND)                     \
+                                       | MACB_BIT(ISR_RLE)             \
+                                       | MACB_BIT(TXERR)               \
+                                       | MACB_BIT(TCOMP))
 
 /* Ring buffer accessors */
 static unsigned int macb_tx_ring_wrap(unsigned int index)
@@ -338,66 +342,56 @@ static void macb_update_stats(struct macb *bp)
                *p += __raw_readl(reg);
 }
 
-static void macb_tx(struct macb *bp)
+static void macb_handle_tx_error(struct macb *bp, unsigned int err_tail, u32 
ctrl)
 {
-       unsigned int tail;
-       unsigned int head;
-       u32 status;
-
-       status = macb_readl(bp, TSR);
-       macb_writel(bp, TSR, status);
+       struct macb_tx_skb      *tx_skb;
+       struct sk_buff          *skb;
+       unsigned int            head = bp->tx_head;
 
-       netdev_vdbg(bp->dev, "macb_tx status = %02lx\n", (unsigned long)status);
+       netdev_dbg(bp->dev, "TX error: ctrl 0x%08x, head %u, error tail %u\n",
+                  ctrl, head, err_tail);
 
-       if (status & (MACB_BIT(UND) | MACB_BIT(TSR_RLE))) {
-               int i;
-               netdev_err(bp->dev, "TX %s, resetting buffers\n",
-                          status & MACB_BIT(UND) ?
-                          "underrun" : "retry limit exceeded");
-
-               /* Transfer ongoing, disable transmitter, to avoid confusion */
-               if (status & MACB_BIT(TGO))
-                       macb_writel(bp, NCR, macb_readl(bp, NCR) & 
~MACB_BIT(TE));
-
-               head = bp->tx_head;
-
-               /*Mark all the buffer as used to avoid sending a lost buffer*/
-               for (i = 0; i < TX_RING_SIZE; i++)
-                       bp->tx_ring[i].ctrl = MACB_BIT(TX_USED);
-
-               /* Add wrap bit */
-               bp->tx_ring[TX_RING_SIZE - 1].ctrl |= MACB_BIT(TX_WRAP);
+       /*
+        * "Buffers exhausted mid-frame" errors may only happen if the
+        * driver is buggy, so complain loudly about those. Statistics
+        * are updated by hardware.
+        */
+       if (ctrl & MACB_BIT(TX_BUF_EXHAUSTED))
+               netdev_err(bp->dev, "BUG: TX buffers exhausted mid-frame\n");
 
-               /* free transmit buffer in upper layer*/
-               for (tail = bp->tx_tail; tail != head; tail++) {
-                       struct macb_tx_skb      *tx_skb;
-                       struct sk_buff          *skb;
+       /*
+        * Drop the frames that caused the error plus all remaining in queue.
+        * Free transmit buffers in upper layer.
+        */
+       for (; err_tail != head; err_tail++) {
+               struct macb_dma_desc    *desc;
 
-                       rmb();
+               tx_skb = macb_tx_skb(bp, err_tail);
+               skb = tx_skb->skb;
+               dma_unmap_single(&bp->pdev->dev, tx_skb->mapping, skb->len,
+                                DMA_TO_DEVICE);
+               dev_kfree_skb_irq(skb);
+               tx_skb->skb = NULL;
 
-                       tx_skb = macb_tx_skb(bp, tail);
-                       skb = tx_skb->skb;
+               desc = macb_tx_desc(bp, err_tail);
+               desc->ctrl |= MACB_BIT(TX_USED);
+       }
 
-                       dma_unmap_single(&bp->pdev->dev, tx_skb->mapping,
-                                               skb->len, DMA_TO_DEVICE);
-                       tx_skb->skb = NULL;
-                       dev_kfree_skb_irq(skb);
-               }
+       /* Make descriptor updates visible to hardware */
+       wmb();
+}
 
-               bp->tx_head = bp->tx_tail = 0;
+static void macb_tx_interrupt(struct macb *bp)
+{
+       unsigned int tail;
+       unsigned int head;
+       u32 status;
 
-               /* Enable the transmitter again */
-               if (status & MACB_BIT(TGO))
-                       macb_writel(bp, NCR, macb_readl(bp, NCR) | 
MACB_BIT(TE));
-       }
+       status = macb_readl(bp, TSR);
+       macb_writel(bp, TSR, status);
 
-       if (!(status & MACB_BIT(COMP)))
-               /*
-                * This may happen when a buffer becomes complete
-                * between reading the ISR and scanning the
-                * descriptors.  Nothing to worry about.
-                */
-               return;
+       netdev_vdbg(bp->dev, "macb_tx_interrupt status = %02lx\n",
+               (unsigned long)status);
 
        head = bp->tx_head;
        for (tail = bp->tx_tail; tail != head; tail++) {
@@ -413,6 +407,31 @@ static void macb_tx(struct macb *bp)
 
                ctrl = desc->ctrl;
 
+               if (unlikely(ctrl & (MACB_BIT(TX_ERROR)
+                                       | MACB_BIT(TX_UNDERRUN)
+                                       | MACB_BIT(TX_BUF_EXHAUSTED)))) {
+                       /*
+                        * In case of transfer ongoing, disable transmitter.
+                        * Should already be the case due to hardware,
+                        * but make sure to avoid confusion.
+                        */
+                       if (status & MACB_BIT(TGO))
+                               macb_writel(bp, NCR, macb_readl(bp, NCR) & 
~MACB_BIT(TE));
+
+                       /*
+                        * An error should always stop the queue from advancing.
+                        * reset entries in the ring and exit from the loop.
+                        */
+                       macb_handle_tx_error(bp, tail, ctrl);
+                       bp->tx_head = bp->tx_tail = head = tail = 0;
+
+                       /* Enable the transmitter again, start TX will be done 
elsewhere */
+                       if (status & MACB_BIT(TGO))
+                               macb_writel(bp, NCR, macb_readl(bp, NCR) | 
MACB_BIT(TE));
+
+                       break;
+               }
+
                if (!(ctrl & MACB_BIT(TX_USED)))
                        break;
 
@@ -644,9 +663,8 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
                        }
                }
 
-               if (status & (MACB_BIT(TCOMP) | MACB_BIT(ISR_TUND) |
-                           MACB_BIT(ISR_RLE)))
-                       macb_tx(bp);
+               if (status & MACB_TX_INT_FLAGS)
+                       macb_tx_interrupt(bp);
 
                /*
                 * Link change detection isn't possible with RMII, so we'll
-- 
1.7.10

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

Reply via email to