From patchwork Mon Jan 28 13:11:08 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pantelis Antoniou X-Patchwork-Id: 2055871 Return-Path: X-Original-To: patchwork-linux-omap@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork2.kernel.org (Postfix) with ESMTP id 00B1AE00C6 for ; Mon, 28 Jan 2013 13:11:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754982Ab3A1NLU (ORCPT ); Mon, 28 Jan 2013 08:11:20 -0500 Received: from li42-95.members.linode.com ([209.123.162.95]:59844 "EHLO li42-95.members.linode.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754091Ab3A1NLT (ORCPT ); Mon, 28 Jan 2013 08:11:19 -0500 Received: from sles11esa.localdomain (unknown [195.97.110.117]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) (Authenticated sender: panto) by li42-95.members.linode.com (Postfix) with ESMTPSA id 9132286C8; Mon, 28 Jan 2013 13:11:11 +0000 (UTC) From: Pantelis Antoniou To: Mugunthan V N Cc: Richard Cochran , Matt Porter , Chase Maupin , Jason Kridner , Tony Lindgren , linux-omap@vger.kernel.org, linux-kernel@vger.kernel.org, Pantelis Antoniou Subject: [PATCH] cpsw: Fix interrupt storm among other things Date: Mon, 28 Jan 2013 15:11:08 +0200 Message-Id: <1359378668-26536-1-git-send-email-panto@antoniou-consulting.com> X-Mailer: git-send-email 1.7.12 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org Fix interrupt storm on bone A4 cause by non-by-the-book interrupt handling. While at it, added a non-NAPI mode (which is easier to debug), plus some general fixes. Signed-off-by: Pantelis Antoniou --- Documentation/devicetree/bindings/net/cpsw.txt | 1 + drivers/net/ethernet/ti/cpsw.c | 222 +++++++++++++++++++++---- drivers/net/ethernet/ti/davinci_cpdma.c | 4 +- drivers/net/ethernet/ti/davinci_cpdma.h | 2 +- include/linux/platform_data/cpsw.h | 1 + 5 files changed, 194 insertions(+), 36 deletions(-) diff --git a/Documentation/devicetree/bindings/net/cpsw.txt b/Documentation/devicetree/bindings/net/cpsw.txt index 6ddd028..d46b293 100644 --- a/Documentation/devicetree/bindings/net/cpsw.txt +++ b/Documentation/devicetree/bindings/net/cpsw.txt @@ -20,6 +20,7 @@ Required properties: - cpts_clock_shift : Denominator to convert input clock ticks into nanoseconds - phy_id : Specifies slave phy id - mac-address : Specifies slave MAC address +- disable-napi : Disables driver NAPI Optional properties: - ti,hwmods : Must be "cpgmac0" diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 40aff68..b6ca4af 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -148,10 +148,37 @@ struct cpsw_wr_regs { u32 soft_reset; u32 control; u32 int_control; - u32 rx_thresh_en; - u32 rx_en; - u32 tx_en; - u32 misc_en; + u32 c0_rx_thresh_en; + u32 c0_rx_en; + u32 c0_tx_en; + u32 c0_misc_en; + u32 c1_rx_thresh_en; + u32 c1_rx_en; + u32 c1_tx_en; + u32 c1_misc_en; + u32 c2_rx_thresh_en; + u32 c2_rx_en; + u32 c2_tx_en; + u32 c2_misc_en; + u32 c0_rx_thresh_stat; + u32 c0_rx_stat; + u32 c0_tx_stat; + u32 c0_misc_stat; + u32 c1_rx_thresh_stat; + u32 c1_rx_stat; + u32 c1_tx_stat; + u32 c1_misc_stat; + u32 c2_rx_thresh_stat; + u32 c2_rx_stat; + u32 c2_tx_stat; + u32 c2_misc_stat; + u32 c0_rx_imax; + u32 c0_tx_imax; + u32 c1_rx_imax; + u32 c1_tx_imax; + u32 c2_rx_imax; + u32 c2_tx_imax; + u32 rgmii_ctl; }; struct cpsw_ss_regs { @@ -352,8 +379,8 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) static void cpsw_intr_enable(struct cpsw_priv *priv) { - __raw_writel(0xFF, &priv->wr_regs->tx_en); - __raw_writel(0xFF, &priv->wr_regs->rx_en); + __raw_writel(0xFF, &priv->wr_regs->c0_tx_en); + __raw_writel(0xFF, &priv->wr_regs->c0_rx_en); cpdma_ctlr_int_ctrl(priv->dma, true); return; @@ -361,8 +388,8 @@ static void cpsw_intr_enable(struct cpsw_priv *priv) static void cpsw_intr_disable(struct cpsw_priv *priv) { - __raw_writel(0, &priv->wr_regs->tx_en); - __raw_writel(0, &priv->wr_regs->rx_en); + __raw_writel(0, &priv->wr_regs->c0_tx_en); + __raw_writel(0, &priv->wr_regs->c0_rx_en); cpdma_ctlr_int_ctrl(priv->dma, false); return; @@ -399,7 +426,10 @@ void cpsw_rx_handler(void *token, int len, int status) skb_put(skb, len); cpts_rx_timestamp(&priv->cpts, skb); skb->protocol = eth_type_trans(skb, ndev); - netif_receive_skb(skb); + if (priv->data.disable_napi) + netif_rx(skb); + else + netif_receive_skb(skb); priv->stats.rx_bytes += len; priv->stats.rx_packets++; skb = NULL; @@ -431,6 +461,7 @@ static irqreturn_t cpsw_interrupt(int irq, void *dev_id) cpsw_disable_irq(priv); napi_schedule(&priv->napi); } + return IRQ_HANDLED; } @@ -445,23 +476,104 @@ static inline int cpsw_get_slave_port(struct cpsw_priv *priv, u32 slave_num) static int cpsw_poll(struct napi_struct *napi, int budget) { struct cpsw_priv *priv = napi_to_priv(napi); - int num_tx, num_rx; + int num_tx, num_rx, num_total_tx, num_total_rx; + int budget_left; + + budget_left = budget; - num_tx = cpdma_chan_process(priv->txch, 128); - num_rx = cpdma_chan_process(priv->rxch, budget); + /* read status and throw away */ + (void)__raw_readl(&priv->wr_regs->c0_tx_stat); + + /* handle all transmits */ + num_total_tx = 0; + while (budget_left > 0 && + (num_tx = cpdma_chan_process(priv->txch, 128)) > 0) { + budget_left -= num_tx; + num_total_tx += num_tx; + } - if (num_rx || num_tx) - cpsw_dbg(priv, intr, "poll %d rx, %d tx pkts\n", - num_rx, num_tx); + if (num_total_tx > 0 && budget_left > 0) + cpdma_ctlr_eoi(priv->dma, 0x02); + + /* read status and throw away */ + (void)__raw_readl(&priv->wr_regs->c0_rx_stat); + + /* handle all receives */ + num_total_rx = 0; + while (budget_left > 0 && + (num_rx = cpdma_chan_process(priv->rxch, budget_left)) > 0) { + budget_left -= num_rx; + num_total_rx += num_rx; + } - if (num_rx < budget) { + if (num_total_rx > 0 && budget_left > 0) + cpdma_ctlr_eoi(priv->dma, 0x01); + + if ((num_total_rx + num_total_tx) < budget) { napi_complete(napi); cpsw_intr_enable(priv); - cpdma_ctlr_eoi(priv->dma); cpsw_enable_irq(priv); } - return num_rx; + return num_total_rx + num_total_rx; +} + +static irqreturn_t cpsw_rx_thresh_pend_irq(int irq, void *dev_id) +{ + struct cpsw_priv *priv = dev_id; + + (void)priv; + + /* not handling this interrupt yet */ + return IRQ_HANDLED; +} + +static irqreturn_t cpsw_rx_pend_irq(int irq, void *dev_id) +{ + struct cpsw_priv *priv = dev_id; + int num_rx, total_rx; + u32 rx_stat; + + rx_stat = __raw_readl(&priv->wr_regs->c0_rx_stat) & 0xff; + if (rx_stat == 0) + return IRQ_NONE; + + total_rx = 0; + while ((num_rx = cpdma_chan_process(priv->rxch, + priv->data.rx_descs)) > 0) + total_rx += num_rx; + + cpdma_ctlr_eoi(priv->dma, 0x01); + + return IRQ_HANDLED; +} + +static irqreturn_t cpsw_tx_pend_irq(int irq, void *dev_id) +{ + struct cpsw_priv *priv = dev_id; + int num_tx, total_tx; + u32 tx_stat; + + tx_stat = __raw_readl(&priv->wr_regs->c0_tx_stat) & 0xff; + if (tx_stat == 0) + return IRQ_NONE; + + total_tx = 0; + while ((num_tx = cpdma_chan_process(priv->txch, 128)) > 0) + total_tx += num_tx; + + cpdma_ctlr_eoi(priv->dma, 0x02); + + return IRQ_HANDLED; +} + +static irqreturn_t cpsw_misc_pend_irq(int irq, void *dev_id) +{ + struct cpsw_priv *priv = dev_id; + + (void)priv; + /* not handling this interrupt yet */ + return IRQ_HANDLED; } static inline void soft_reset(const char *module, void __iomem *reg) @@ -678,8 +790,10 @@ static int cpsw_ndo_open(struct net_device *ndev) cpdma_ctlr_start(priv->dma); cpsw_intr_enable(priv); - napi_enable(&priv->napi); - cpdma_ctlr_eoi(priv->dma); + if (!priv->data.disable_napi) + napi_enable(&priv->napi); + cpdma_ctlr_eoi(priv->dma, 0x01); + cpdma_ctlr_eoi(priv->dma, 0x02); return 0; } @@ -698,8 +812,10 @@ static int cpsw_ndo_stop(struct net_device *ndev) struct cpsw_priv *priv = netdev_priv(ndev); cpsw_info(priv, ifdown, "shutting down cpsw device\n"); + cpsw_disable_irq(priv); netif_stop_queue(priv->ndev); - napi_disable(&priv->napi); + if (!priv->data.disable_napi) + napi_disable(&priv->napi); netif_carrier_off(priv->ndev); cpsw_intr_disable(priv); cpdma_ctlr_int_ctrl(priv->dma, false); @@ -901,7 +1017,8 @@ static void cpsw_ndo_tx_timeout(struct net_device *ndev) cpdma_chan_start(priv->txch); cpdma_ctlr_int_ctrl(priv->dma, true); cpsw_intr_enable(priv); - cpdma_ctlr_eoi(priv->dma); + cpdma_ctlr_eoi(priv->dma, 0x01); + cpdma_ctlr_eoi(priv->dma, 0x02); } static struct net_device_stats *cpsw_ndo_get_stats(struct net_device *ndev) @@ -917,14 +1034,21 @@ static void cpsw_ndo_poll_controller(struct net_device *ndev) cpsw_intr_disable(priv); cpdma_ctlr_int_ctrl(priv->dma, false); - cpsw_interrupt(ndev->irq, priv); + if (!priv->data.disable_napi) + cpsw_interrupt(ndev->irq, priv); + else { + /* bah! */ + cpsw_rx_pend_irq(ndev->irq, priv); + cpsw_tx_pend_irq(ndev->irq, priv); + } cpdma_ctlr_int_ctrl(priv->dma, true); cpsw_intr_enable(priv); - cpdma_ctlr_eoi(priv->dma); + cpdma_ctlr_eoi(priv->dma, 0x01); + cpdma_ctlr_eoi(priv->dma, 0x02); } #endif -static const struct net_device_ops cpsw_netdev_ops = { +static struct net_device_ops cpsw_netdev_ops = { .ndo_open = cpsw_ndo_open, .ndo_stop = cpsw_ndo_stop, .ndo_start_xmit = cpsw_ndo_start_xmit, @@ -1079,6 +1203,9 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, } data->bd_ram_size = prop; + if (of_property_read_bool(node, "disable-napi")) + data->disable_napi = 1; + if (of_property_read_u32(node, "rx_descs", &prop)) { pr_err("Missing rx_descs property in the DT.\n"); ret = -EINVAL; @@ -1136,6 +1263,22 @@ error_ret: return ret; } +static irq_handler_t cpsw_get_irq_handler(struct cpsw_priv *priv, int irq_idx) +{ + static const irq_handler_t non_napi_irq_tab[4] = { + cpsw_rx_thresh_pend_irq, cpsw_rx_pend_irq, + cpsw_tx_pend_irq, cpsw_misc_pend_irq + }; + + if ((unsigned int)irq_idx >= 4) + return NULL; + + if (!priv->data.disable_napi) + return cpsw_interrupt; + + return non_napi_irq_tab[irq_idx]; +} + static int cpsw_probe(struct platform_device *pdev) { struct cpsw_platform_data *data = pdev->dev.platform_data; @@ -1146,7 +1289,8 @@ static int cpsw_probe(struct platform_device *pdev) void __iomem *ss_regs, *wr_regs; struct resource *res; u32 slave_offset, sliver_offset, slave_size; - int ret = 0, i, k = 0; + irq_handler_t irqh; + int ret = 0, i, j, k = 0; ndev = alloc_etherdev(sizeof(struct cpsw_priv)); if (!ndev) { @@ -1333,24 +1477,36 @@ static int cpsw_probe(struct platform_device *pdev) goto clean_ale_ret; } - while ((res = platform_get_resource(priv->pdev, IORESOURCE_IRQ, k))) { - for (i = res->start; i <= res->end; i++) { - if (request_irq(i, cpsw_interrupt, IRQF_DISABLED, + dev_info(&pdev->dev, "NAPI %s\n", priv->data.disable_napi ? + "disabled" : "enabled"); + + /* get interrupts */ + j = k = 0; + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, j++))) { + for (i = res->start; k < 4 && i <= res->end; i++) { + irqh = cpsw_get_irq_handler(priv, k); + if (irqh == NULL) { + dev_err(&pdev->dev, "Unable to get handler " + "for #%d (%d)\n", k, i); + goto clean_ale_ret; + } + if (request_irq(i, irqh, IRQF_DISABLED, dev_name(&pdev->dev), priv)) { dev_err(priv->dev, "error attaching irq\n"); goto clean_ale_ret; } - priv->irqs_table[k] = i; - priv->num_irqs = k; + priv->irqs_table[k++] = i; } - k++; } + priv->num_irqs = k; ndev->flags |= IFF_ALLMULTI; /* see cpsw_ndo_change_rx_flags() */ ndev->netdev_ops = &cpsw_netdev_ops; SET_ETHTOOL_OPS(ndev, &cpsw_ethtool_ops); - netif_napi_add(ndev, &priv->napi, cpsw_poll, CPSW_POLL_WEIGHT); + + if (!priv->data.disable_napi) + netif_napi_add(ndev, &priv->napi, cpsw_poll, CPSW_POLL_WEIGHT); /* register the network device */ SET_NETDEV_DEV(ndev, &pdev->dev); diff --git a/drivers/net/ethernet/ti/davinci_cpdma.c b/drivers/net/ethernet/ti/davinci_cpdma.c index 4995673..6effab2 100644 --- a/drivers/net/ethernet/ti/davinci_cpdma.c +++ b/drivers/net/ethernet/ti/davinci_cpdma.c @@ -474,9 +474,9 @@ int cpdma_ctlr_int_ctrl(struct cpdma_ctlr *ctlr, bool enable) return 0; } -void cpdma_ctlr_eoi(struct cpdma_ctlr *ctlr) +void cpdma_ctlr_eoi(struct cpdma_ctlr *ctlr, u32 value) { - dma_reg_write(ctlr, CPDMA_MACEOIVECTOR, 0); + dma_reg_write(ctlr, CPDMA_MACEOIVECTOR, value); } struct cpdma_chan *cpdma_chan_create(struct cpdma_ctlr *ctlr, int chan_num, diff --git a/drivers/net/ethernet/ti/davinci_cpdma.h b/drivers/net/ethernet/ti/davinci_cpdma.h index afa19a0..b7c097d 100644 --- a/drivers/net/ethernet/ti/davinci_cpdma.h +++ b/drivers/net/ethernet/ti/davinci_cpdma.h @@ -86,7 +86,7 @@ int cpdma_chan_submit(struct cpdma_chan *chan, void *token, void *data, int cpdma_chan_process(struct cpdma_chan *chan, int quota); int cpdma_ctlr_int_ctrl(struct cpdma_ctlr *ctlr, bool enable); -void cpdma_ctlr_eoi(struct cpdma_ctlr *ctlr); +void cpdma_ctlr_eoi(struct cpdma_ctlr *ctlr, u32 value); int cpdma_chan_int_ctrl(struct cpdma_chan *chan, bool enable); enum cpdma_control { diff --git a/include/linux/platform_data/cpsw.h b/include/linux/platform_data/cpsw.h index 24368a2..be5b27e 100644 --- a/include/linux/platform_data/cpsw.h +++ b/include/linux/platform_data/cpsw.h @@ -35,6 +35,7 @@ struct cpsw_platform_data { u32 bd_ram_size; /*buffer descriptor ram size */ u32 rx_descs; /* Number of Rx Descriptios */ u32 mac_control; /* Mac control register */ + int disable_napi; /* disable NAPI */ }; #endif /* __CPSW_H__ */