[PATCH net 2/4] net: bcmgenet: do not tear the datapath down twice after a failed resume

From: Nicolai Buchwitz

Date: Sat Oct 17 2026 - 08:01:15 EST


If bcmgenet_resume() fails in bcmgenet_init_dma() it returns with the
interface still IFF_UP. The suspend before it already deleted the NAPIs and
freed rx_cbs and tx_cbs, so the next ifdown runs the whole teardown again
and napi_disable() waits for a NAPI_STATE_SCHED bit that nothing will
clear:

task:ip state:D
usleep_range_state+0xec/0x158
napi_disable_locked+0x254/0x318
napi_disable+0x3c/0x68
bcmgenet_netif_stop+0x1c8/0x318
bcmgenet_close+0x24/0xd0
__dev_close_many+0x18c/0x310

RTNL is held across the wait, so every other rtnl_lock() user blocks behind
it. A second suspend ends up in the same place.

Flagged in a sashiko review, found plausible and then reproduced on a CM4.

Clear rx_cbs and tx_cbs as they are freed, so close and suspend can tell
there is nothing left to tear down.

The WoL resume path needs the same check. It reuses the rings when it sees
the receiver enabled. But after a failed resume the MAC sits in software
reset, and the next link event takes it out and enables it. Reusing rings
that are gone reattaches the interface and the first transmit hits
BUG_ON(!tx_cb_ptr), so check the rings too.

Fixes: b6e978e50444 ("net: bcmgenet: add suspend/resume callbacks")
Signed-off-by: Nicolai Buchwitz <nb@xxxxxxxxxxx>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 308716cddc077..fe3338ba81980 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -3107,7 +3107,9 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv)
bcmgenet_free_rx_buffers(priv);
bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
+ priv->rx_cbs = NULL;
kfree(priv->tx_cbs);
+ priv->tx_cbs = NULL;
}

/* init_edma: Initialize DMA control register */
@@ -3165,6 +3167,7 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx)
priv->tx_cbs = kzalloc_objs(struct enet_cb, priv->num_tx_bds);
if (!priv->tx_cbs) {
kfree(priv->rx_cbs);
+ priv->rx_cbs = NULL;
return -ENOMEM;
}

@@ -3184,7 +3187,9 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx)
bcmgenet_free_rx_buffers(priv);
bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
+ priv->rx_cbs = NULL;
kfree(priv->tx_cbs);
+ priv->tx_cbs = NULL;
return ret;
}

@@ -3493,7 +3498,9 @@ static int bcmgenet_close(struct net_device *dev)

netif_dbg(priv, ifdown, dev, "bcmgenet_close\n");

- bcmgenet_netif_stop(dev, false);
+ /* A failed resume has torn the datapath down already */
+ if (priv->tx_cbs)
+ bcmgenet_netif_stop(dev, false);

/* Really kill the PHY state machine and disconnect from it */
phy_disconnect(dev->phydev);
@@ -4258,7 +4265,11 @@ static int bcmgenet_resume(struct device *d)

if (device_may_wakeup(d) && priv->wolopts) {
reg = bcmgenet_umac_readl(priv, UMAC_CMD);
- if (reg & CMD_RX_EN) {
+
+ /* A link event can enable the receiver after a failed
+ * resume, so check the rings too
+ */
+ if (priv->tx_cbs && (reg & CMD_RX_EN)) {
/* Successfully exited WoL, just resume data flows */
list_for_each_entry(rule, &priv->rxnfc_list, list)
if (rule->state == BCMGENET_RXNFC_STATE_ENABLED)
@@ -4343,6 +4354,15 @@ static int bcmgenet_suspend(struct device *d)
if (!netif_running(dev))
return 0;

+ /* A failed resume has torn the datapath down already. Resume still
+ * enables irq1 on the WoL path, so it has to be disabled here.
+ */
+ if (!priv->tx_cbs) {
+ if (device_may_wakeup(d) && priv->wolopts)
+ disable_irq(priv->irq1);
+ return 0;
+ }
+
netif_device_detach(dev);

if (device_may_wakeup(d) && priv->wolopts) {

--
2.53.0