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 <pa...@antoniou-consulting.com>
---
 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__ */
-- 
1.7.12

--
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