From patchwork Wed Oct 31 16:04:57 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Ferre X-Patchwork-Id: 1680131 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork2.kernel.org (Postfix) with ESMTP id B2D12DFE75 for ; Wed, 31 Oct 2012 16:10:21 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TTapa-0001aE-GL; Wed, 31 Oct 2012 16:07:53 +0000 Received: from eusmtp01.atmel.com ([212.144.249.243]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1TTanh-0000ia-O9 for linux-arm-kernel@lists.infradead.org; Wed, 31 Oct 2012 16:05:56 +0000 Received: from tenerife.corp.atmel.com (10.161.101.13) by eusmtp01.atmel.com (10.161.101.31) with Microsoft SMTP Server id 14.2.318.4; Wed, 31 Oct 2012 17:06:04 +0100 From: Nicolas Ferre To: , , Subject: [PATCH v4 08/10] net/macb: better manage tx errors Date: Wed, 31 Oct 2012 17:04:57 +0100 Message-ID: <9146955236335d47b748b56fd1d3c5f050eb089e.1351690694.git.nicolas.ferre@atmel.com> X-Mailer: git-send-email 1.8.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20121031_120554_533753_E2138024 X-CRM114-Status: GOOD ( 19.74 ) X-Spam-Score: 0.4 (/) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (0.4 points) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 KHOP_BIG_TO_CC Sent to 10+ recipients instaed of Bcc or a list -0.0 SPF_PASS SPF: sender matches SPF record -0.7 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Nicolas Ferre , manabian@gmail.com, patrice.vilchez@atmel.com, linux-kernel@vger.kernel.org, David.Laight@ACULAB.COM, bhutchings@solarflare.com, plagnioj@jcrosoft.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org Handle all TX errors, not only underruns. TX error management is deferred to a dedicated workqueue. Reinitialize the TX ring after treating all remaining frames, and restart the controller when everything has been cleaned up properly. Napi is not stopped during this task as the driver only handles napi for RX for now. With this sequence, we do not need a special check during the xmit method as the packets will be caught by TX disable during workqueue execution. Signed-off-by: Nicolas Ferre Tested-by: Joachim Eastwood --- drivers/net/ethernet/cadence/macb.c | 166 ++++++++++++++++++++++++------------ drivers/net/ethernet/cadence/macb.h | 1 + 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c index 78488b4..d5b52ff 100644 --- a/drivers/net/ethernet/cadence/macb.c +++ b/drivers/net/ethernet/cadence/macb.c @@ -44,6 +44,16 @@ #define MACB_RX_INT_FLAGS (MACB_BIT(RCOMP) | MACB_BIT(RXUBR) \ | MACB_BIT(ISR_ROVR)) +#define MACB_TX_ERR_FLAGS (MACB_BIT(ISR_TUND) \ + | MACB_BIT(ISR_RLE) \ + | MACB_BIT(TXERR)) +#define MACB_TX_INT_FLAGS (MACB_TX_ERR_FLAGS | MACB_BIT(TCOMP)) + +/* + * Graceful stop timeouts in us. We should allow up to + * 1 frame time (10 Mbits/s, full-duplex, ignoring collisions) + */ +#define MACB_HALT_TIMEOUT 1230 /* Ring buffer accessors */ static unsigned int macb_tx_ring_wrap(unsigned int index) @@ -339,66 +349,113 @@ static void macb_update_stats(struct macb *bp) *p += __raw_readl(reg); } -static void macb_tx(struct macb *bp) +static int macb_halt_tx(struct macb *bp) { - unsigned int tail; - unsigned int head; - u32 status; + unsigned long halt_time, timeout; + u32 status; - status = macb_readl(bp, TSR); - macb_writel(bp, TSR, status); + macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(THALT)); - netdev_vdbg(bp->dev, "macb_tx status = 0x%03lx\n", (unsigned long)status); + timeout = jiffies + usecs_to_jiffies(MACB_HALT_TIMEOUT); + do { + halt_time = jiffies; + status = macb_readl(bp, TSR); + if (!(status & MACB_BIT(TGO))) + return 0; - 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"); + usleep_range(10, 250); + } while (time_before(halt_time, timeout)); - /* Transfer ongoing, disable transmitter, to avoid confusion */ - if (status & MACB_BIT(TGO)) - macb_writel(bp, NCR, macb_readl(bp, NCR) & ~MACB_BIT(TE)); + return -ETIMEDOUT; +} - head = bp->tx_head; +static void macb_tx_error_task(struct work_struct *work) +{ + struct macb *bp = container_of(work, struct macb, tx_error_task); + struct macb_tx_skb *tx_skb; + struct sk_buff *skb; + unsigned int tail; - /*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); + netdev_vdbg(bp->dev, "macb_tx_error_task: t = %u, h = %u\n", + bp->tx_tail, bp->tx_head); - /* Add wrap bit */ - bp->tx_ring[TX_RING_SIZE - 1].ctrl |= MACB_BIT(TX_WRAP); + /* Make sure nobody is trying to queue up new packets */ + netif_stop_queue(bp->dev); - /* free transmit buffer in upper layer*/ - for (tail = bp->tx_tail; tail != head; tail++) { - struct macb_tx_skb *tx_skb; - struct sk_buff *skb; + /* + * Stop transmission now + * (in case we have just queued new packets) + */ + if (macb_halt_tx(bp)) + /* Just complain for now, reinitializing TX path can be good */ + netdev_err(bp->dev, "BUG: halt tx timed out\n"); - rmb(); + /* No need for the lock here as nobody will interrupt us anymore */ - tx_skb = macb_tx_skb(bp, tail); - skb = tx_skb->skb; + /* + * Treat frames in TX queue including the ones that caused the error. + * Free transmit buffers in upper layer. + */ + for (tail = bp->tx_tail; tail != bp->tx_head; tail++) { + struct macb_dma_desc *desc; + u32 ctrl; - dma_unmap_single(&bp->pdev->dev, tx_skb->mapping, - skb->len, DMA_TO_DEVICE); - tx_skb->skb = NULL; - dev_kfree_skb_irq(skb); - } + desc = macb_tx_desc(bp, tail); + ctrl = desc->ctrl; + tx_skb = macb_tx_skb(bp, tail); + skb = tx_skb->skb; - bp->tx_head = bp->tx_tail = 0; + if (ctrl & MACB_BIT(TX_USED)) { + netdev_vdbg(bp->dev, "txerr skb %u (data %p) TX complete\n", + macb_tx_ring_wrap(tail), skb->data); + bp->stats.tx_packets++; + bp->stats.tx_bytes += skb->len; + } else { + /* + * "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"); - /* Enable the transmitter again */ - if (status & MACB_BIT(TGO)) - macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TE)); + desc->ctrl = 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(skb); } - 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; + /* Make descriptor updates visible to hardware */ + wmb(); + + /* Reinitialize the TX desc queue */ + macb_writel(bp, TBQP, bp->tx_ring_dma); + /* Make TX ring reflect state of hardware */ + bp->tx_head = bp->tx_tail = 0; + + /* Now we are ready to start transmission again */ + netif_wake_queue(bp->dev); + + /* Housework before enabling TX IRQ */ + macb_writel(bp, TSR, macb_readl(bp, TSR)); + macb_writel(bp, IER, MACB_TX_INT_FLAGS); +} + +static void macb_tx_interrupt(struct macb *bp) +{ + unsigned int tail; + unsigned int head; + u32 status; + + status = macb_readl(bp, TSR); + macb_writel(bp, TSR, status); + + netdev_vdbg(bp->dev, "macb_tx_interrupt status = 0x%03lx\n", + (unsigned long)status); head = bp->tx_head; for (tail = bp->tx_tail; tail != head; tail++) { @@ -638,9 +695,14 @@ 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 (unlikely(status & (MACB_TX_ERR_FLAGS))) { + macb_writel(bp, IDR, MACB_TX_INT_FLAGS); + schedule_work(&bp->tx_error_task); + break; + } + + if (status & MACB_BIT(TCOMP)) + macb_tx_interrupt(bp); /* * Link change detection isn't possible with RMII, so we'll @@ -970,13 +1032,8 @@ static void macb_init_hw(struct macb *bp) macb_writel(bp, NCR, MACB_BIT(RE) | MACB_BIT(TE) | MACB_BIT(MPE)); /* Enable interrupts */ - macb_writel(bp, IER, (MACB_BIT(RCOMP) - | MACB_BIT(RXUBR) - | MACB_BIT(ISR_TUND) - | MACB_BIT(ISR_RLE) - | MACB_BIT(TXERR) - | MACB_BIT(TCOMP) - | MACB_BIT(ISR_ROVR) + macb_writel(bp, IER, (MACB_RX_INT_FLAGS + | MACB_TX_INT_FLAGS | MACB_BIT(HRESP))); } @@ -1428,6 +1485,7 @@ static int __init macb_probe(struct platform_device *pdev) bp->dev = dev; spin_lock_init(&bp->lock); + INIT_WORK(&bp->tx_error_task, macb_tx_error_task); bp->pclk = clk_get(&pdev->dev, "pclk"); if (IS_ERR(bp->pclk)) { diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 232dca6..4235ab8 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -538,6 +538,7 @@ struct macb { struct clk *hclk; struct net_device *dev; struct napi_struct napi; + struct work_struct tx_error_task; struct net_device_stats stats; union { struct macb_stats macb;