From 84e040b3af0cf4c1f9f975b37da7b598b6f4d0fa Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 6 Apr 2026 10:57:54 -0700 Subject: [PATCH 1/4] net: bcmgenet: fix off-by-one in bcmgenet_put_txcb The write_ptr points to the next open tx_cb. We want to return the tx_cb that gets rewinded, so we must rewind the pointer first then return the tx_cb that it points to. That way the txcb can be correctly cleaned up. Fixes: 876dbadd53a7 ("net: bcmgenet: Fix unmapping of fragments in bcmgenet_xmit()") Signed-off-by: Justin Chen Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260406175756.134567-2-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 3b8a062004b733..304101d6820e3b 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1815,15 +1815,15 @@ static struct enet_cb *bcmgenet_put_txcb(struct bcmgenet_priv *priv, { struct enet_cb *tx_cb_ptr; - tx_cb_ptr = ring->cbs; - tx_cb_ptr += ring->write_ptr - ring->cb_ptr; - /* Rewinding local write pointer */ if (ring->write_ptr == ring->cb_ptr) ring->write_ptr = ring->end_ptr; else ring->write_ptr--; + tx_cb_ptr = ring->cbs; + tx_cb_ptr += ring->write_ptr - ring->cb_ptr; + return tx_cb_ptr; } From 9608f1874254f015fe0e2e3fd0831957e3f858d1 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 6 Apr 2026 10:57:55 -0700 Subject: [PATCH 2/4] net: bcmgenet: fix leaking free_bds While reclaiming the tx queue we fast forward the write pointer to drop any data in flight. These dropped frames are not added back to the pool of free bds. We also need to tell the netdev that we are dropping said data. Fixes: f1bacae8b655 ("net: bcmgenet: support reclaiming unsent Tx packets") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Reviewed-by: Nicolai Buchwitz Tested-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260406175756.134567-3-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 304101d6820e3b..091ffcff72f8fb 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1981,6 +1981,7 @@ static unsigned int bcmgenet_tx_reclaim(struct net_device *dev, drop = (ring->prod_index - ring->c_index) & DMA_C_INDEX_MASK; released += drop; ring->prod_index = ring->c_index & DMA_C_INDEX_MASK; + ring->free_bds += drop; while (drop--) { cb_ptr = bcmgenet_put_txcb(priv, ring); skb = cb_ptr->skb; @@ -1992,6 +1993,7 @@ static unsigned int bcmgenet_tx_reclaim(struct net_device *dev, } if (skb) dev_consume_skb_any(skb); + netdev_tx_reset_queue(netdev_get_tx_queue(dev, ring->index)); bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index, TDMA_PROD_INDEX); wr_ptr = ring->write_ptr * WORDS_PER_BD(priv); From 11eec3cd2a95863fee9c7b005dc11dc642aa1a7b Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 6 Apr 2026 10:57:56 -0700 Subject: [PATCH 3/4] net: bcmgenet: fix racing timeout handler The bcmgenet_timeout handler tries to take down all tx queues when a single queue times out. This is over zealous and causes many race conditions with queues that are still chugging along. Instead lets only restart the timed out queue. Fixes: 13ea657806cf ("net: bcmgenet: improve TX timeout") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Reviewed-by: Nicolai Buchwitz Tested-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260406175756.134567-4-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 091ffcff72f8fb..b0d7fa3e517e47 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3475,27 +3475,23 @@ static void bcmgenet_dump_tx_queue(struct bcmgenet_tx_ring *ring) static void bcmgenet_timeout(struct net_device *dev, unsigned int txqueue) { struct bcmgenet_priv *priv = netdev_priv(dev); - u32 int1_enable = 0; - unsigned int q; + struct bcmgenet_tx_ring *ring = &priv->tx_rings[txqueue]; + struct netdev_queue *txq = netdev_get_tx_queue(dev, txqueue); netif_dbg(priv, tx_err, dev, "bcmgenet_timeout\n"); - for (q = 0; q <= priv->hw_params->tx_queues; q++) - bcmgenet_dump_tx_queue(&priv->tx_rings[q]); - - bcmgenet_tx_reclaim_all(dev); + bcmgenet_dump_tx_queue(ring); - for (q = 0; q <= priv->hw_params->tx_queues; q++) - int1_enable |= (1 << q); + bcmgenet_tx_reclaim(dev, ring, true); - /* Re-enable TX interrupts if disabled */ - bcmgenet_intrl2_1_writel(priv, int1_enable, INTRL2_CPU_MASK_CLEAR); + /* Re-enable the TX interrupt for this ring */ + bcmgenet_intrl2_1_writel(priv, 1 << txqueue, INTRL2_CPU_MASK_CLEAR); - netif_trans_update(dev); + txq_trans_cond_update(txq); - BCMGENET_STATS64_INC((&priv->tx_rings[txqueue].stats64), errors); + BCMGENET_STATS64_INC((&ring->stats64), errors); - netif_tx_wake_all_queues(dev); + netif_tx_wake_queue(txq); } #define MAX_MDF_FILTER 17 From d126e159e5c1efa734cb5a7afa3c9c7e5247acd3 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 19 May 2026 08:48:07 +0200 Subject: [PATCH 4/4] debug rbuf_/tbuf_pm Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index b0d7fa3e517e47..f7f44dd5aa588b 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -66,6 +66,26 @@ /* Forward declarations */ static void bcmgenet_set_rx_mode(struct net_device *dev); +/* + * Diagnostic knob for isolating GENET EEE/LPI failures. + * + * Bit 0: UMAC EEE protocol handling + * Bit 1: TBUF EEE/power-management + * Bit 2: RBUF EEE/power-management + * + */ +#define GENET_EEE_PM_UMAC BIT(0) +#define GENET_EEE_PM_TBUF BIT(1) +#define GENET_EEE_PM_RBUF BIT(2) +#define GENET_EEE_PM_ALL (GENET_EEE_PM_UMAC | \ + GENET_EEE_PM_TBUF | \ + GENET_EEE_PM_RBUF) + +static uint eee_pm_mask = GENET_EEE_PM_ALL; +module_param(eee_pm_mask, uint, 0444); +MODULE_PARM_DESC(eee_pm_mask, + "EEE power-management mask: bit0=UMAC bit1=TBUF bit2=RBUF"); + static inline void bcmgenet_writel(u32 value, void __iomem *offset) { /* MIPS chips strapped for BE will automagically configure the @@ -1347,15 +1367,18 @@ void bcmgenet_eee_enable_set(struct net_device *dev, bool enable) { struct bcmgenet_priv *priv = netdev_priv(dev); u32 off = priv->hw_params->tbuf_offset + TBUF_ENERGY_CTRL; + u32 mask = enable ? eee_pm_mask : 0; u32 reg; - if (enable && !priv->clk_eee_enabled) { + netdev_info_once(dev, "GENET EEE PM mask: 0x%x\n", eee_pm_mask); + + if (mask && !priv->clk_eee_enabled) { clk_prepare_enable(priv->clk_eee); priv->clk_eee_enabled = true; } reg = bcmgenet_umac_readl(priv, UMAC_EEE_CTRL); - if (enable) + if (mask & GENET_EEE_PM_UMAC) reg |= EEE_EN; else reg &= ~EEE_EN; @@ -1363,7 +1386,7 @@ void bcmgenet_eee_enable_set(struct net_device *dev, bool enable) /* Enable EEE and switch to a 27Mhz clock automatically */ reg = bcmgenet_readl(priv->base + off); - if (enable) + if (mask & GENET_EEE_PM_TBUF) reg |= TBUF_EEE_EN | TBUF_PM_EN; else reg &= ~(TBUF_EEE_EN | TBUF_PM_EN); @@ -1371,13 +1394,13 @@ void bcmgenet_eee_enable_set(struct net_device *dev, bool enable) /* Do the same for thing for RBUF */ reg = bcmgenet_rbuf_readl(priv, RBUF_ENERGY_CTRL); - if (enable) + if (mask & GENET_EEE_PM_RBUF) reg |= RBUF_EEE_EN | RBUF_PM_EN; else reg &= ~(RBUF_EEE_EN | RBUF_PM_EN); bcmgenet_rbuf_writel(priv, reg, RBUF_ENERGY_CTRL); - if (!enable && priv->clk_eee_enabled) { + if (!mask && priv->clk_eee_enabled) { clk_disable_unprepare(priv->clk_eee); priv->clk_eee_enabled = false; }