mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust
@ 2026-11-06 19:14 Joe Damato
  2026-11-06 19:14 ` [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers Joe Damato
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Joe Damato @ 2026-11-06 19:14 UTC (permalink / raw)
  To: netdev
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
	michael.chan, pavan.chebbi, linux-kernel, Joe Damato

Greetings:

This is a follow up to the previous RFC (linked below).

On two production systems, I saw the following dmesg pattern:

  NETDEV WATCHDOG: transmit queue 0 timed out 6073 ms
  Resp cmpl intr err msg: 0x51                  x20
  hwrm_ring_free type 1 failed                  x12
  hwrm_ring_free type 2 failed                  x8
  AMD-Vi: IO_PAGE_FAULT  x3

This suggests that, for some currently unknown reason, TX completions stall
and the netdev watchdog fires. The driver asks FW to free the rings, this
times out, but the driver ignores the possible failure and frees ring memory.
Since the FW didn't respond to the ring free command, it is possible that the
FW is still DMAing to the memory which was freed.

Since the RFCv4, I found a third system that hit this repeatedly:

  NETDEV WATCHDOG: CPU: 35: transmit queue 0 timed out 5459 ms
  hwrm_ring_free type 1 failed. rc:fffffff0 err:0    x64
  hwrm_ring_free type 2 failed. rc:fffffff0 err:0    x32
  hwrm_ring_free type 0 failed. rc:fffffffb err:1    x64
  hwrm_ring_alloc type 1 failed. rc:ffffffe4 err:4
  hwrm_ring_free type 1 failed. rc:fffffff0 err:0    x44
  Abandoning msg {0x51 0x3e35} len: 0 due to firmware status: 0x408100
  nic open fail (rc: fffffffb)
  DMAR: [DMA Write NO_PASID] Request device [47:00.0] fault addr
  0xffffa07ef000 [fault reason 0x05] PTE Write access is not set

This case differs from the first two in a few ways, but this series would have
still helped:

 - The ring frees fail on the close path and again on the error path of
   the reopen (bnxt_init_chip()), and the device then writes to memory the
   driver has already unmapped. The series stops DMA on both paths.

 - The interface came back up later, the watchdog fired again, and the
   cycle started over. The host logged 79 DMAR write faults in about 20
   hours. Patch 4 breaks this loop by refusing to reopen after DMA has
   been stopped.

 - The firmware answers each RING_FREE about 5 seconds late, after the
   driver has given up, and finally reports an error in its health
   register. Patch 2 does not treat these late responses as success because
   their sequence ids do not match.

In this most recent case, I suspect the hardware is faulty, but the driver
shouldn't hand memory back to the system while the device could still write to
it.

In all of the above cases, the IOMMU caught the error. If the system had no
IOMMU or the IOMMU was in passthrough mode, data corruption or crash would
have occurred.

In the future, this could possibly be extended to make recovery automatic.

Thanks,
Joe

v5:
 - Switched from RFC to actual submission
 - Patch 4 updated to use an un-used bit to make backporting easier.

v4: https://lore.kernel.org/netdev/20260925174404.2789072-1-joe@dama.to/
  - No changes to patch 1 or 2
  - Patch 3: reworded the message logged when DMA is stopped, a rebind is
    what recovers the device, not a firmware reset. No functional change.
  - Patch 4 added which adds a new bit (BNXT_STATE_DMA_STOPPED) that is set
    when DMA is disabled. When this bit is set, the device requires user
    intervention to bring back up.

v3: https://lore.kernel.org/netdev/20260923210744.3406861-1-joe@dama.to/
  - No changes to patch 1
  - Patch 2: Don't poll for the valid bit as Michael suggested.
  - Patch 3: bnxt_hwrm_ring_free now returns -EIO instead of stopping the
    device, so the remaining resources can be freed and the remaining commands
    can be sent before stopping the device, as Michael suggested. Note the
    switch to using pci_clear_master in this patch instead of
    pci_disable_device. This was done so that the normal shutdown paths can
    call pci_disable_device without generating a warning.

v2: https://lore.kernel.org/netdev/20260922182405.1290749-1-joe@dama.to/
  - No changes to patch 1
  - Patch 2 from v1 dropped
  - Patch 2 in the v2 now checks the response and logs state before giving up
  - Patch 3 in the v2 disables the device to stop DMA before freeing ring
    memory

RFCv1: https://lore.kernel.org/netdev/20260917233218.1160001-1-joe@dama.to/


Joe Damato (4):
  bnxt_en: return the RING_FREE status to callers
  bnxt_en: check HWRM response if completion never arrives
  bnxt_en: stop DMA before releasing rings the firmware did not free
  bnxt_en: refuse to open a device with stopped DMA

 drivers/net/ethernet/broadcom/bnxt/bnxt.c     | 122 ++++++++++++------
 drivers/net/ethernet/broadcom/bnxt/bnxt.h     |   1 +
 .../net/ethernet/broadcom/bnxt/bnxt_hwrm.c    |  33 ++++-
 3 files changed, 115 insertions(+), 41 deletions(-)


base-commit: 19a227903e65c7a9c179156485660a4e017575fe
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers
  2026-11-06 19:14 [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust Joe Damato
@ 2026-11-06 19:14 ` Joe Damato
  2026-11-07 19:58   ` netdev-bot+sashiko
  2026-11-06 19:14 ` [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives Joe Damato
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Joe Damato @ 2026-11-06 19:14 UTC (permalink / raw)
  To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Prashant Sreedharan
  Cc: edumazet, horms, linux-kernel, Joe Damato

hwrm_ring_free_send_msg() reports failure to its caller, returning -EIO
when the firmware rejects HWRM_RING_FREE or never answers it. All three
ring free helpers that send the command discard the value.

Return it instead. No caller acts on it yet, so there is no functional
change.

Fixes: 74608fc98d28 ("bnxt_en: Ring free response from close path should use completion ring")
Signed-off-by: Joe Damato <joe@dama.to>
---
 drivers/net/ethernet/broadcom/bnxt/bnxt.c | 48 +++++++++++++----------
 1 file changed, 27 insertions(+), 21 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 33c4cacc8ffe..ea8529c78994 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -7679,50 +7679,55 @@ static int hwrm_ring_free_send_msg(struct bnxt *bp,
 	return 0;
 }
 
-static void bnxt_hwrm_tx_ring_free(struct bnxt *bp,
-				   struct bnxt_tx_ring_info *txr,
-				   bool close_path)
+static int bnxt_hwrm_tx_ring_free(struct bnxt *bp,
+				  struct bnxt_tx_ring_info *txr,
+				  bool close_path)
 {
 	struct bnxt_ring_struct *ring = &txr->tx_ring_struct;
 	u32 cmpl_ring_id;
+	int rc;
 
 	if (ring->fw_ring_id == INVALID_HW_RING_ID)
-		return;
+		return 0;
 
 	cmpl_ring_id = close_path ? bnxt_cp_ring_for_tx(bp, txr) :
 		       INVALID_HW_RING_ID;
-	hwrm_ring_free_send_msg(bp, ring, RING_FREE_REQ_RING_TYPE_TX,
-				cmpl_ring_id);
+	rc = hwrm_ring_free_send_msg(bp, ring, RING_FREE_REQ_RING_TYPE_TX,
+				     cmpl_ring_id);
 	ring->fw_ring_id = INVALID_HW_RING_ID;
+	return rc;
 }
 
-static void bnxt_hwrm_rx_ring_free(struct bnxt *bp,
-				   struct bnxt_rx_ring_info *rxr,
-				   bool close_path)
+static int bnxt_hwrm_rx_ring_free(struct bnxt *bp,
+				  struct bnxt_rx_ring_info *rxr,
+				  bool close_path)
 {
 	struct bnxt_ring_struct *ring = &rxr->rx_ring_struct;
 	u32 grp_idx = rxr->bnapi->index;
 	u32 cmpl_ring_id;
+	int rc;
 
 	if (ring->fw_ring_id == INVALID_HW_RING_ID)
-		return;
+		return 0;
 
 	cmpl_ring_id = bnxt_cp_ring_for_rx(bp, rxr);
-	hwrm_ring_free_send_msg(bp, ring,
-				RING_FREE_REQ_RING_TYPE_RX,
-				close_path ? cmpl_ring_id :
-				INVALID_HW_RING_ID);
+	rc = hwrm_ring_free_send_msg(bp, ring,
+				     RING_FREE_REQ_RING_TYPE_RX,
+				     close_path ? cmpl_ring_id :
+				     INVALID_HW_RING_ID);
 	ring->fw_ring_id = INVALID_HW_RING_ID;
 	bp->grp_info[grp_idx].rx_fw_ring_id = INVALID_HW_RING_ID;
+	return rc;
 }
 
-static void bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
-				       struct bnxt_rx_ring_info *rxr,
-				       bool close_path)
+static int bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
+				      struct bnxt_rx_ring_info *rxr,
+				      bool close_path)
 {
 	struct bnxt_ring_struct *ring = &rxr->rx_agg_ring_struct;
 	u32 grp_idx = rxr->bnapi->index;
 	u32 type, cmpl_ring_id;
+	int rc;
 
 	if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS)
 		type = RING_FREE_REQ_RING_TYPE_RX_AGG;
@@ -7730,14 +7735,15 @@ static void bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
 		type = RING_FREE_REQ_RING_TYPE_RX;
 
 	if (ring->fw_ring_id == INVALID_HW_RING_ID)
-		return;
+		return 0;
 
 	cmpl_ring_id = bnxt_cp_ring_for_rx(bp, rxr);
-	hwrm_ring_free_send_msg(bp, ring, type,
-				close_path ? cmpl_ring_id :
-				INVALID_HW_RING_ID);
+	rc = hwrm_ring_free_send_msg(bp, ring, type,
+				     close_path ? cmpl_ring_id :
+				     INVALID_HW_RING_ID);
 	ring->fw_ring_id = INVALID_HW_RING_ID;
 	bp->grp_info[grp_idx].agg_fw_ring_id = INVALID_HW_RING_ID;
+	return rc;
 }
 
 static void bnxt_hwrm_cp_ring_free(struct bnxt *bp,
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives
  2026-11-06 19:14 [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust Joe Damato
  2026-11-06 19:14 ` [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers Joe Damato
@ 2026-11-06 19:14 ` Joe Damato
  2026-11-07 19:58   ` netdev-bot+sashiko
  2026-11-06 19:14 ` [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free Joe Damato
  2026-11-06 19:14 ` [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA Joe Damato
  3 siblings, 1 reply; 9+ messages in thread
From: Joe Damato @ 2026-11-06 19:14 UTC (permalink / raw)
  To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Prashant Sreedharan
  Cc: edumazet, horms, linux-kernel, Joe Damato

When a command is sent over a completion ring, __hwrm_send() waits for
NAPI to consume the completion and gives up if it never arrives, without
looking at the response.

If a completion is not posted within the timeout, check the response
before giving up. If resp_len is set, the sequence id matches, and the
valid byte is set then the firmware completed the command and only the
notification was lost. Fall through to the normal error_code handling in
that case.

Several seconds are spent waiting for the completion, so a response that
was written at all is complete by the time the wait gives up. There is no
need to poll for the valid byte here the way the polling path below has
to, where the poll is for a non-zero length and the valid byte at the end
of the message may still be on its way.

Log the response state on both paths so there is more data when this rare
event occurs.

Fixes: 74608fc98d28 ("bnxt_en: Ring free response from close path should use completion ring")
Signed-off-by: Joe Damato <joe@dama.to>
---
 .../net/ethernet/broadcom/bnxt/bnxt_hwrm.c    | 33 ++++++++++++++++---
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
index 5bfabdca7d0e..4feba90f0bf6 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
@@ -582,11 +582,36 @@ static int __hwrm_send(struct bnxt *bp, struct bnxt_hwrm_ctx *ctx)
 		}
 
 		if (READ_ONCE(token->state) != BNXT_HWRM_COMPLETE) {
-			hwrm_err(bp, ctx, "Resp cmpl intr err msg: 0x%x\n",
-				 req_type);
-			goto exit;
+			__le16 resp_seq_id;
+			u8 valid_byte = 0;
+
+			/* The completion ring entry was not delivered for
+			 * some reason. It might be possible that the command
+			 * was carried out even without a completion being
+			 * posted. Check the response before giving up and log
+			 * the state.
+			 */
+			dma_rmb();
+			resp_seq_id = READ_ONCE(ctx->resp->seq_id);
+			len = le16_to_cpu(READ_ONCE(ctx->resp->resp_len));
+			if (len && resp_seq_id == ctx->req->seq_id)
+				valid_byte = *((u8 *)ctx->resp + len - 1);
+
+			if (!valid_byte) {
+				hwrm_err(bp, ctx,
+					 "Resp cmpl intr err msg: 0x%x len:%d seq:0x%x/0x%x\n",
+					 req_type, len,
+					 le16_to_cpu(resp_seq_id),
+					 le16_to_cpu(ctx->req->seq_id));
+				goto exit;
+			}
+			netdev_warn(bp->dev,
+				    "Resp cmpl intr not delivered, msg: 0x%x completed anyway (len:%d valid:0x%x err:0x%x)\n",
+				    req_type, len, valid_byte,
+				    le16_to_cpu(ctx->resp->error_code));
+		} else {
+			len = le16_to_cpu(READ_ONCE(ctx->resp->resp_len));
 		}
-		len = le16_to_cpu(READ_ONCE(ctx->resp->resp_len));
 		valid = ((u8 *)ctx->resp) + len - 1;
 	} else {
 		__le16 seen_out_of_seq = ctx->req->seq_id; /* will never see */
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free
  2026-11-06 19:14 [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust Joe Damato
  2026-11-06 19:14 ` [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers Joe Damato
  2026-11-06 19:14 ` [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives Joe Damato
@ 2026-11-06 19:14 ` Joe Damato
  2026-11-07 19:58   ` netdev-bot+sashiko
  2026-11-06 19:14 ` [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA Joe Damato
  3 siblings, 1 reply; 9+ messages in thread
From: Joe Damato @ 2026-11-06 19:14 UTC (permalink / raw)
  To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Prashant Sreedharan
  Cc: edumazet, horms, linux-kernel, Joe Damato

When HWRM_RING_FREE is not answered, bnxt_hwrm_ring_free() clears
fw_ring_id and __bnxt_close_nic() goes on to call bnxt_free_mem(), which
unmaps the ring memory and the RX buffers that the FW may still be
using.

This is reachable in production. On a BCM57504 the first sign is the TX
watchdog; the close that follows times out a subset of its RING_FREEs and
the driver releases those rings anyway:

  05:30:12  NETDEV WATCHDOG: transmit queue 0 timed out 6073 ms
  05:30:12  Resp cmpl intr err msg: 0x51                  x20
  05:30:12  hwrm_ring_free type 1 failed                  x12
  05:30:12  hwrm_ring_free type 2 failed                  x8
  05:30:12  AMD-Vi: IO_PAGE_FAULT  x3

Count the rings the firmware did not free and report that to the caller
so it can decide what to do. bnxt_hwrm_resource_free() still frees the
remaining firmware resources before returning the error, so the shutdown
can send every message it needs to before the device is stopped.

The device stays unusable until the driver is rebound.

Fixes: 74608fc98d28 ("bnxt_en: Ring free response from close path should use completion ring")
Signed-off-by: Joe Damato <joe@dama.to>
---
 drivers/net/ethernet/broadcom/bnxt/bnxt.c | 48 +++++++++++++++++------
 1 file changed, 35 insertions(+), 13 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index ea8529c78994..b5ef86bf92bf 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -7773,21 +7773,25 @@ static void bnxt_clear_one_cp_ring(struct bnxt *bp, struct bnxt_cp_ring_info *cp
 			memset(cpr->cp_desc_ring[i], 0, size);
 }
 
-static void bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
+static int bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
 {
+	int stuck = 0;
 	u32 type;
 	int i;
 
 	if (!bp->bnapi)
-		return;
+		return 0;
 
 	for (i = 0; i < bp->tx_nr_rings; i++)
-		bnxt_hwrm_tx_ring_free(bp, &bp->tx_ring[i], close_path);
+		if (bnxt_hwrm_tx_ring_free(bp, &bp->tx_ring[i], close_path))
+			stuck++;
 
 	bnxt_cancel_dim(bp);
 	for (i = 0; i < bp->rx_nr_rings; i++) {
-		bnxt_hwrm_rx_ring_free(bp, &bp->rx_ring[i], close_path);
-		bnxt_hwrm_rx_agg_ring_free(bp, &bp->rx_ring[i], close_path);
+		if (bnxt_hwrm_rx_ring_free(bp, &bp->rx_ring[i], close_path))
+			stuck++;
+		if (bnxt_hwrm_rx_agg_ring_free(bp, &bp->rx_ring[i], close_path))
+			stuck++;
 	}
 
 	/* The completion rings are about to be freed.  After that the
@@ -7817,6 +7821,19 @@ static void bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
 			bp->grp_info[i].cp_fw_ring_id = INVALID_HW_RING_ID;
 		}
 	}
+
+	if (!stuck)
+		return 0;
+
+	netdev_err(bp->dev, "Firmware did not free %d ring(s)\n", stuck);
+	return -EIO;
+}
+
+static void bnxt_stop_dma(struct bnxt *bp)
+{
+	netdev_err(bp->dev,
+		   "Disabling DMA before releasing ring memory, the driver must be rebound to recover\n");
+	pci_clear_master(bp->pdev);
 }
 
 static int __bnxt_trim_rings(struct bnxt *bp, int *rx, int *tx, int max,
@@ -10851,16 +10868,19 @@ static void bnxt_clear_vnic(struct bnxt *bp)
 		bnxt_hwrm_vnic_ctx_free(bp);
 }
 
-static void bnxt_hwrm_resource_free(struct bnxt *bp, bool close_path,
-				    bool irq_re_init)
+static int bnxt_hwrm_resource_free(struct bnxt *bp, bool close_path,
+				   bool irq_re_init)
 {
+	int rc;
+
 	bnxt_clear_vnic(bp);
-	bnxt_hwrm_ring_free(bp, close_path);
+	rc = bnxt_hwrm_ring_free(bp, close_path);
 	bnxt_hwrm_ring_grp_free(bp);
 	if (irq_re_init) {
 		bnxt_hwrm_stat_ctx_free(bp);
 		bnxt_hwrm_free_tunnel_ports(bp);
 	}
+	return rc;
 }
 
 static int bnxt_hwrm_set_br_mode(struct bnxt *bp, u16 br_mode)
@@ -11382,15 +11402,15 @@ static int bnxt_init_chip(struct bnxt *bp, bool irq_re_init)
 	return 0;
 
 err_out:
-	bnxt_hwrm_resource_free(bp, 0, true);
+	if (bnxt_hwrm_resource_free(bp, 0, true))
+		bnxt_stop_dma(bp);
 
 	return rc;
 }
 
 static int bnxt_shutdown_nic(struct bnxt *bp, bool irq_re_init)
 {
-	bnxt_hwrm_resource_free(bp, 1, irq_re_init);
-	return 0;
+	return bnxt_hwrm_resource_free(bp, 1, irq_re_init);
 }
 
 static int bnxt_init_nic(struct bnxt *bp, bool irq_re_init)
@@ -13461,7 +13481,8 @@ int bnxt_half_open_nic(struct bnxt *bp)
  */
 void bnxt_half_close_nic(struct bnxt *bp)
 {
-	bnxt_hwrm_resource_free(bp, false, true);
+	if (bnxt_hwrm_resource_free(bp, false, true))
+		bnxt_stop_dma(bp);
 	bnxt_del_napi(bp);
 	bnxt_free_skbs(bp);
 	bnxt_free_mem(bp, true);
@@ -13540,7 +13561,8 @@ static void __bnxt_close_nic(struct bnxt *bp, bool irq_re_init,
 	if (BNXT_SUPPORTS_MULTI_RSS_CTX(bp))
 		bnxt_clear_rss_ctxs(bp);
 	/* Flush rings and disable interrupts */
-	bnxt_shutdown_nic(bp, irq_re_init);
+	if (bnxt_shutdown_nic(bp, irq_re_init))
+		bnxt_stop_dma(bp);
 
 	/* TODO CHIMP_FW: Link/PHY related cleanup if (link_re_init) */
 
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA
  2026-11-06 19:14 [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust Joe Damato
                   ` (2 preceding siblings ...)
  2026-11-06 19:14 ` [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free Joe Damato
@ 2026-11-06 19:14 ` Joe Damato
  2026-11-07 19:58   ` netdev-bot+sashiko
  3 siblings, 1 reply; 9+ messages in thread
From: Joe Damato @ 2026-11-06 19:14 UTC (permalink / raw)
  To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Prashant Sreedharan
  Cc: edumazet, horms, linux-kernel, Joe Damato

Add BNXT_STATE_DMA_STOPPED and set it in bnxt_stop_dma() to signal that
the device had DMA disabled. When opening the device later, check this
bit and exit with an error.

This is a very rare case, but when it happens the device needs manual
intervention by the user to become usable again.

Fixes: 74608fc98d28 ("bnxt_en: Ring free response from close path should use completion ring")
Signed-off-by: Joe Damato <joe@dama.to>
---
 drivers/net/ethernet/broadcom/bnxt/bnxt.c | 26 ++++++++++++++++++++---
 drivers/net/ethernet/broadcom/bnxt/bnxt.h |  1 +
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index b5ef86bf92bf..eb885fd74064 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -7833,9 +7833,20 @@ static void bnxt_stop_dma(struct bnxt *bp)
 {
 	netdev_err(bp->dev,
 		   "Disabling DMA before releasing ring memory, the driver must be rebound to recover\n");
+	set_bit(BNXT_STATE_DMA_STOPPED, &bp->state);
 	pci_clear_master(bp->pdev);
 }
 
+static int bnxt_check_dma_stopped(struct bnxt *bp)
+{
+	if (!test_bit(BNXT_STATE_DMA_STOPPED, &bp->state))
+		return 0;
+
+	netdev_err(bp->dev,
+		   "DMA was disabled after the firmware failed to free rings, rebind the driver to recover\n");
+	return -ENODEV;
+}
+
 static int __bnxt_trim_rings(struct bnxt *bp, int *rx, int *tx, int max,
 			     bool shared);
 static int bnxt_trim_rings(struct bnxt *bp, int *rx, int *tx, int max,
@@ -13426,9 +13437,10 @@ static int __bnxt_open_nic(struct bnxt *bp, bool irq_re_init, bool link_re_init)
 
 int bnxt_open_nic(struct bnxt *bp, bool irq_re_init, bool link_re_init)
 {
-	int rc = 0;
+	int rc;
 
-	if (test_bit(BNXT_STATE_ABORT_ERR, &bp->state))
+	rc = bnxt_check_dma_stopped(bp);
+	if (!rc && test_bit(BNXT_STATE_ABORT_ERR, &bp->state))
 		rc = -EIO;
 	if (!rc)
 		rc = __bnxt_open_nic(bp, irq_re_init, link_re_init);
@@ -13445,7 +13457,11 @@ int bnxt_open_nic(struct bnxt *bp, bool irq_re_init, bool link_re_init)
  */
 int bnxt_half_open_nic(struct bnxt *bp)
 {
-	int rc = 0;
+	int rc;
+
+	rc = bnxt_check_dma_stopped(bp);
+	if (rc)
+		goto half_open_err;
 
 	if (test_bit(BNXT_STATE_ABORT_ERR, &bp->state)) {
 		netdev_err(bp->dev, "A previous firmware reset has not completed, aborting half open\n");
@@ -13505,6 +13521,10 @@ static int bnxt_open(struct net_device *dev)
 	struct bnxt *bp = netdev_priv(dev);
 	int rc;
 
+	rc = bnxt_check_dma_stopped(bp);
+	if (rc)
+		return rc;
+
 	if (test_bit(BNXT_STATE_ABORT_ERR, &bp->state)) {
 		rc = bnxt_reinit_after_abort(bp);
 		if (rc) {
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index 41d05efd6307..7e65b61b6bf8 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -2474,6 +2474,7 @@ struct bnxt {
 #define BNXT_STATE_FW_NON_FATAL_COND	13
 #define BNXT_STATE_FW_ACTIVATE_RESET	14
 #define BNXT_STATE_HALF_OPEN		15	/* For offline ethtool tests */
+#define BNXT_STATE_DMA_STOPPED		16
 
 #define BNXT_NO_FW_ACCESS(bp)					\
 	(test_bit(BNXT_STATE_FW_FATAL_COND, &(bp)->state) ||	\
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers
  2026-11-06 19:14 ` [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers Joe Damato
@ 2026-11-07 19:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-11-07 19:58 UTC (permalink / raw)
  To: joe
  Cc: netdev, michael.chan, pavan.chebbi, andrew+netdev, davem,
	edumazet, kuba, pabeni, prashant, edumazet, horms, linux-kernel

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The patch makes bnxt_hwrm_rx_ring_free(),
  bnxt_hwrm_rx_agg_ring_free() and bnxt_hwrm_tx_ring_free() return the…
- [Medium] The commit message says "All three ring free helpers that send
  the command discard the value", but four helpers send HWRM_RING_FREE,…

--

bnxt_en: return the RING_FREE status to callers

This commit changes bnxt_hwrm_tx_ring_free(), bnxt_hwrm_rx_ring_free()
and bnxt_hwrm_rx_agg_ring_free() so they return the status from
hwrm_ring_free_send_msg() instead of discarding it. No caller acts on
the returned value yet.

> diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> index 33c4cacc8ffe3..ea8529c789947 100644
> --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> @@ -7679,50 +7679,55 @@ static int hwrm_ring_free_send_msg(struct bnxt *bp,

[ ... ]

> -static void bnxt_hwrm_rx_ring_free(struct bnxt *bp,
> -				   struct bnxt_rx_ring_info *rxr,
> -				   bool close_path)
> +static int bnxt_hwrm_rx_ring_free(struct bnxt *bp,
> +				  struct bnxt_rx_ring_info *rxr,
> +				  bool close_path)
>  {
>  	struct bnxt_ring_struct *ring = &rxr->rx_ring_struct;
>  	u32 grp_idx = rxr->bnapi->index;
>  	u32 cmpl_ring_id;
> +	int rc;
>  
>  	if (ring->fw_ring_id == INVALID_HW_RING_ID)
> -		return;
> +		return 0;
>  
>  	cmpl_ring_id = bnxt_cp_ring_for_rx(bp, rxr);
> -	hwrm_ring_free_send_msg(bp, ring,
> -				RING_FREE_REQ_RING_TYPE_RX,
> -				close_path ? cmpl_ring_id :
> -				INVALID_HW_RING_ID);
> +	rc = hwrm_ring_free_send_msg(bp, ring,
> +				     RING_FREE_REQ_RING_TYPE_RX,
> +				     close_path ? cmpl_ring_id :
> +				     INVALID_HW_RING_ID);
>  	ring->fw_ring_id = INVALID_HW_RING_ID;
>  	bp->grp_info[grp_idx].rx_fw_ring_id = INVALID_HW_RING_ID;
> +	return rc;
>  }

[Severity: High]
The per-queue callers of these helpers still ignore the returned status,
and none of the later patches in the series seems to change them. Can
the queue restart path still release rings that the firmware did not
free?

In bnxt_queue_stop():

	bnxt_hwrm_rx_ring_free(bp, rxr, false);
	bnxt_hwrm_rx_agg_ring_free(bp, rxr, false);
	...
	memcpy(qmem, rxr, sizeof(*rxr));
	bnxt_init_rx_ring_struct(bp, qmem);

	return 0;

If the firmware rejects HWRM_RING_FREE or it times out, ndo_queue_stop
still returns 0. netdev_rx_queue_reconfig() then calls
ndo_queue_mem_free() on the old queue memory.

bnxt_queue_mem_free() then frees the RX buffers, destroys the page pools
and frees the RX and AGG descriptor rings. The firmware ring may still be
live at that point.

With tph_mode set, bnxt_tx_queue_stop() does the same thing for TX:

		bnxt_hwrm_tx_ring_free(bp, txr, true);
		bnxt_hwrm_cp_ring_free(bp, txr->tx_cpr);
		bnxt_free_one_tx_ring_skbs(bp, txr, txr->txq_index);

The helpers also clear ring->fw_ring_id even when rc is non-zero. After
that, the stale firmware ring is no longer visible to the stuck ring
accounting that the later patch "bnxt_en: stop DMA before releasing
rings the firmware did not free" adds to bnxt_hwrm_ring_free(). Would a
later close then skip bnxt_stop_dma() for that ring?

This path can be reached through netdev_rx_queue_restart(), either from
devmem TCP or io_uring zcrx memory provider binding, or from the bnxt
TPH IRQ affinity work.

> -static void bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
> -				       struct bnxt_rx_ring_info *rxr,
> -				       bool close_path)
> +static int bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
> +				      struct bnxt_rx_ring_info *rxr,
> +				      bool close_path)

[ ... ]

> @@ -7730,14 +7735,15 @@ static void bnxt_hwrm_rx_agg_ring_free(struct bnxt *bp,
>  		type = RING_FREE_REQ_RING_TYPE_RX;
>  
>  	if (ring->fw_ring_id == INVALID_HW_RING_ID)
> -		return;
> +		return 0;
>  
>  	cmpl_ring_id = bnxt_cp_ring_for_rx(bp, rxr);
> -	hwrm_ring_free_send_msg(bp, ring, type,
> -				close_path ? cmpl_ring_id :
> -				INVALID_HW_RING_ID);
> +	rc = hwrm_ring_free_send_msg(bp, ring, type,
> +				     close_path ? cmpl_ring_id :
> +				     INVALID_HW_RING_ID);
>  	ring->fw_ring_id = INVALID_HW_RING_ID;
>  	bp->grp_info[grp_idx].agg_fw_ring_id = INVALID_HW_RING_ID;
> +	return rc;
>  }
>  
>  static void bnxt_hwrm_cp_ring_free(struct bnxt *bp,

[Severity: Medium]
The commit message says:

    All three ring free helpers that send the command discard the value.

Is that count right? bnxt_hwrm_cp_ring_free() also sends HWRM_RING_FREE,
and it still returns void:

	hwrm_ring_free_send_msg(bp, ring, RING_FREE_REQ_RING_TYPE_L2_CMPL,
				INVALID_HW_RING_ID);
	ring->fw_ring_id = INVALID_HW_RING_ID;

The inline NQ/L2_CMPL free in bnxt_hwrm_ring_free() also drops its
status:

		if (ring->fw_ring_id != INVALID_HW_RING_ID) {
			hwrm_ring_free_send_msg(bp, ring, type,
						INVALID_HW_RING_ID);

By the end of the series, bnxt_hwrm_ring_free() counts stuck rings only
for TX, RX and AGG failures. If a CP or NQ RING_FREE fails, would
bnxt_stop_dma() be skipped, with __bnxt_close_nic() going on to free
that ring's memory in bnxt_free_mem()?

Ring 0 of the CP/NQ set is also registered as the firmware async event
completion ring by bnxt_hwrm_set_async_event_cr() in
bnxt_hwrm_ring_alloc(). The close path does not move that registration.

It is not certain that firmware keeps writing to an unfreed CP/NQ ring
after the TX and RX rings are gone. Async event completions are the
likely source if it does.

On the per-queue paths, bnxt_tx_queue_stop() and bnxt_queue_stop() (with
tph_mode) call the converted helpers right next to
bnxt_hwrm_cp_ring_free(). The CP ring free status is not available to
those callers either.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261106191423.4194081-1-joe%40dama.to

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives
  2026-11-06 19:14 ` [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives Joe Damato
@ 2026-11-07 19:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-11-07 19:58 UTC (permalink / raw)
  To: joe
  Cc: netdev, michael.chan, pavan.chebbi, andrew+netdev, davem,
	edumazet, kuba, pabeni, prashant, edumazet, horms, linux-kernel

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 3 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The new fallback for a missing completion is in the generic
  completion-ring wait branch of __hwrm_send()…
- [Medium] bnxt_hwrm_rx_ring_reset()
  (drivers/net/ethernet/broadcom/bnxt/bnxt.c:14467) sends…
- [Medium] In the new fallback for a missing completion in __hwrm_send(),
  dma_rmb() runs once, before the loads of seq_id, resp_len and the…
- [Low] The commit message says the valid byte need not be polled because
  'Several seconds are spent waiting for the completion, so a response…
- [Low] BNXT_HWRM_CTX_SILENT is documented in bnxt_hwrm.h as 'squelch
  firmware errors', and hwrm_err() turns messages into netdev_dbg when…

--

bnxt_en: check HWRM response if completion never arrives

This changes __hwrm_send() so that when a command sent over a completion
ring never has its completion consumed by NAPI, the DMA response is
checked before giving up. If resp_len is non-zero, the sequence id
matches and the valid byte is set, the command is treated as completed
and falls through to the normal error_code handling.

> Several seconds are spent waiting for the completion, so a response that
> was written at all is complete by the time the wait gives up. There is no
> need to poll for the valid byte here the way the polling path below has
> to, where the poll is for a non-zero length and the valid byte at the end
> of the message may still be on its way.

[Severity: Low]
Is the "several seconds" part accurate? __hwrm_req_init() sets the
default timeout like this:

    ctx->timeout = bp->hwrm_cmd_timeout ?: DFLT_HWRM_CMD_TIMEOUT;

DFLT_HWRM_CMD_TIMEOUT is defined as 500 in bnxt.h, and
bp->hwrm_cmd_timeout comes from the firmware's def_req_timeout. None of
the completion ring callers (ring free, ring reset, selftest irq) seem to
raise it.

The wait loop can also end with no timeout at all when
bnxt_free_hwrm_resources() cancels the token:

    WRITE_ONCE(token->state, BNXT_HWRM_CANCELLED);

That also enters the new != BNXT_HWRM_COMPLETE branch. And if firmware
finishes right at the timeout boundary, the response DMA may still be in
flight when it is checked.

A partly written response still reads valid_byte == 0 and takes the error
exit, so the code fails safe. Could the justification be reworded, though?

> diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
> index 5bfabdca7d0e2..4feba90f0bf68 100644
> --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
> +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c
> @@ -582,11 +582,36 @@ static int __hwrm_send(struct bnxt *bp, struct bnxt_hwrm_ctx *ctx)
>  		}
>  
>  		if (READ_ONCE(token->state) != BNXT_HWRM_COMPLETE) {
> -			hwrm_err(bp, ctx, "Resp cmpl intr err msg: 0x%x\n",
> -				 req_type);
> -			goto exit;
> +			__le16 resp_seq_id;
> +			u8 valid_byte = 0;
> +
> +			/* The completion ring entry was not delivered for
> +			 * some reason. It might be possible that the command
> +			 * was carried out even without a completion being
> +			 * posted. Check the response before giving up and log
> +			 * the state.
> +			 */
> +			dma_rmb();
> +			resp_seq_id = READ_ONCE(ctx->resp->seq_id);
> +			len = le16_to_cpu(READ_ONCE(ctx->resp->resp_len));
> +			if (len && resp_seq_id == ctx->req->seq_id)
> +				valid_byte = *((u8 *)ctx->resp + len - 1);

[Severity: Medium]
Is the dma_rmb() in the right place here? It runs once, before the
seq_id, resp_len and valid byte loads. Nothing orders the loads of the
rest of the response after the valid byte is seen.

Those later reads include ctx->resp->error_code in the netdev_warn()
below. They also include the common tail of __hwrm_send():

    *valid = 0;
    rc = le16_to_cpu(ctx->resp->error_code);

and any response fields that callers read after hwrm_req_send() returns.

Firmware writes the valid byte last. The usual consumer pattern is to
see the valid byte, then dma_rmb(), then read the payload. The address of
the valid byte load depends on len, but the error_code load depends on
nothing.

On weakly ordered CPUs such as arm64 or POWER, could the error_code load
complete before the valid byte load while firmware is still writing the
response at the timeout boundary? The response buffer starts zeroed, so a
stale error_code would read as 0 and a failed command would look like a
success.

For HWRM_RING_FREE, would that hide a failure from the error handling
added later in the series, which stops DMA only when ring free fails?

The polling path has the same gap after its valid loop. This patch adds
a new consumer that relies on the completeness argument in the commit
message.

> +
> +			if (!valid_byte) {
> +				hwrm_err(bp, ctx,
> +					 "Resp cmpl intr err msg: 0x%x len:%d seq:0x%x/0x%x\n",
> +					 req_type, len,
> +					 le16_to_cpu(resp_seq_id),
> +					 le16_to_cpu(ctx->req->seq_id));
> +				goto exit;
> +			}

[Severity: Medium]
This fallback is in the generic completion ring branch, so it covers
every request that sets cmpl_ring, not only HWRM_RING_FREE. Does it change
the result of the ethtool interrupt self test?

bnxt_hwrm_selftest_irq() sends HWRM_SELFTEST_IRQ with the completion ring
set:

    req->cmpl_ring = cpu_to_le16(cmpl_ring);
    return hwrm_req_send(bp, req);

The test catches a broken interrupt path only because hwrm_req_send()
fails when NAPI never consumes the HWRM_DONE.

Firmware still writes the DMA response for the command. If the interrupt
or completion never reaches NAPI, this path now falls through and returns
error_code, which is 0:

ethtool -t <dev> offline
  bnxt_self_test()
    bnxt_test_irq()
      bnxt_hwrm_selftest_irq()
        __hwrm_send()

bnxt_self_test() marks the test failed only here:

    if (rc || bnxt_test_irq(bp)) {
        buf[BNXT_IRQ_TEST_IDX] = 1;
        etest->flags |= ETH_TEST_FL_FAILED;
    }

Would Interrupt_test then pass on a device whose interrupt delivery is
broken, with only a netdev_warn in dmesg? The commit message mentions
only the ring free case.

[Severity: Medium]
bnxt_hwrm_rx_ring_reset() raises a similar question. It sends
HWRM_RING_RESET with cmpl_ring set to the RX ring's own completion ring,
using hwrm_req_send_silent().

Before this patch, a lost completion returned -EBUSY. bnxt_rx_ring_reset()
then fell back to bnxt_reset_task(), which also re-initializes NAPI and
IRQs:

    rc = bnxt_hwrm_rx_ring_reset(bp, i);
    if (rc) {
        ...
        bnxt_reset_task(bp, true);
        break;
    }
    bnxt_free_one_rx_ring_skbs(bp, rxr);
    ...
    rxr->bnapi->in_reset = false;

Now the same case can return 0 and take the in-place recovery path.

Commit 74608fc98d28 sent the response through the ring's own completion
ring for ordering: "The response will be the last entry in the ring and
we can free the ring after getting the response." When NAPI consumes
HWRM_DONE, that shows the earlier RX completions on the ring were
discarded while in_reset was set.

Without that, can unconsumed completions stay on the cp ring and be
processed by bnxt_rx_pkt() after in_reset is cleared and the buffers are
replaced?

If the completion was lost because the ring's interrupt or NAPI is stuck,
would the in-place path leave the ring broken, where the global reset
would have recovered it?

What happens once stale entries are processed depends on runtime state,
so this one is less certain.

> +			netdev_warn(bp->dev,
> +				    "Resp cmpl intr not delivered, msg: 0x%x completed anyway (len:%d valid:0x%x err:0x%x)\n",
> +				    req_type, len, valid_byte,
> +				    le16_to_cpu(ctx->resp->error_code));

[Severity: Low]
Should this respect BNXT_HWRM_CTX_SILENT? The flag is documented as
"squelch firmware errors". The failure side of this branch uses
hwrm_err(), which drops to netdev_dbg for silent contexts.

This netdev_warn() always prints the firmware error_code. The common tail
of __hwrm_send() suppresses the same error_code for silent callers:

    if (rc == HWRM_ERR_CODE_BUSY && !(ctx->flags & BNXT_HWRM_CTX_SILENT))
        netdev_warn(bp->dev, "FW returned busy, hwrm req_type 0x%x\n",
                    req_type);
    else if (rc && rc != HWRM_ERR_CODE_PF_UNAVAILABLE)
        hwrm_err(bp, ctx, "hwrm req_type 0x%x seq id 0x%x error 0x%x\n",
                 req_type, le16_to_cpu(ctx->req->seq_id), rc);

bnxt_hwrm_rx_ring_reset() uses hwrm_req_send_silent() and goes through
this path. A lost completion there would now log a warning that includes
the firmware error code.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261106191423.4194081-1-joe%40dama.to

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free
  2026-11-06 19:14 ` [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free Joe Damato
@ 2026-11-07 19:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-11-07 19:58 UTC (permalink / raw)
  To: joe
  Cc: netdev, michael.chan, pavan.chebbi, andrew+netdev, davem,
	edumazet, kuba, pabeni, prashant, edumazet, horms, linux-kernel

Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.

Critical: 0 · High: 2 · Medium: 4 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The fix leaves out completion/NQ rings and stat contexts.
- [High] The queue-restart path still frees ring memory after a RING_FREE
  failure.
- [Medium] bnxt_stop_dma() calls pci_clear_master() on the whole PCI
  function without quiescing anything else that uses it.
- [Medium] The trigger for an irreversible bus-master disable is broader
  than the commit justifies.
- [Medium] Bus mastering can be turned back on while stale firmware rings
  may still exist.
- [Medium] RING_FREE failures that only happen because a firmware reset
  is in progress are counted as stuck rings, and in the final series…

--

bnxt_en: stop DMA before releasing rings the firmware did not free

This commit makes bnxt_hwrm_ring_free() count TX, RX and RX AGG rings
whose HWRM_RING_FREE failed and return -EIO when any failed. The error
goes up through bnxt_hwrm_resource_free() to the close, half-close and
init_chip error paths. Those paths then call a new bnxt_stop_dma() helper,
which clears PCI bus mastering before ring memory is released.

> diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> index ea8529c789947..b5ef86bf92bf5 100644
> --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> @@ -7773,21 +7773,25 @@ static void bnxt_clear_one_cp_ring(struct bnxt *bp, struct bnxt_cp_ring_info *cp
>  			memset(cpr->cp_desc_ring[i], 0, size);
>  }
>  
> -static void bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
> +static int bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
>  {
> +	int stuck = 0;
>  	u32 type;
>  	int i;
>  
>  	if (!bp->bnapi)
> -		return;
> +		return 0;
>  
>  	for (i = 0; i < bp->tx_nr_rings; i++)
> -		bnxt_hwrm_tx_ring_free(bp, &bp->tx_ring[i], close_path);
> +		if (bnxt_hwrm_tx_ring_free(bp, &bp->tx_ring[i], close_path))
> +			stuck++;

[Severity: Medium]
Does every non-zero return here really mean the firmware did not free the
ring? hwrm_ring_free_send_msg() returns the same -EIO for several
different cases:

hwrm_ring_free_send_msg() {
	...
	if (rc || error_code) {
		netdev_err(bp->dev, "hwrm_ring_free type %d failed. rc:%x err:%x\n",
			   ring_type, rc, error_code);
		return -EIO;
	}
	...
}

A timeout ends up here. So does a firmware reply with a non-zero
error_code, for example a rejection of a ring ID the firmware no longer
tracks. Local failures from __hwrm_send() also end up here: -EBUSY when
BNXT_NO_FW_ACCESS() becomes true, or -ENOMEM from token acquisition.

The commit message only makes the case for "When HWRM_RING_FREE is not
answered". An explicit error reply means the firmware processed the
command.

Before this patch such a reply was only logged and the device could be
reopened. Now it leads to bnxt_stop_dma() and the driver has to be
rebound. Should stuck only count the case where no response arrived?

>  
>  	bnxt_cancel_dim(bp);
>  	for (i = 0; i < bp->rx_nr_rings; i++) {
> -		bnxt_hwrm_rx_ring_free(bp, &bp->rx_ring[i], close_path);
> -		bnxt_hwrm_rx_agg_ring_free(bp, &bp->rx_ring[i], close_path);
> +		if (bnxt_hwrm_rx_ring_free(bp, &bp->rx_ring[i], close_path))
> +			stuck++;
> +		if (bnxt_hwrm_rx_agg_ring_free(bp, &bp->rx_ring[i], close_path))
> +			stuck++;
>  	}

[Severity: High]
Earlier in the series these helpers were changed to return the RING_FREE
status, but the queue restart path still ignores it. Can that path free
ring memory after an unanswered RING_FREE?

bnxt_queue_stop() {
	...
	bnxt_hwrm_rx_ring_free(bp, rxr, false);
	bnxt_hwrm_rx_agg_ring_free(bp, rxr, false);
	...
	if (bp->tph_mode) {
		bnxt_hwrm_cp_ring_free(bp, rxr->rx_cpr);
		bnxt_clear_one_cp_ring(bp, rxr->rx_cpr);
	}
	...
	return 0;
}

With TPH, bnxt_tx_queue_stop() does the same for TX, then unmaps the TX
buffers straight away:

		bnxt_hwrm_tx_ring_free(bp, txr, true);
		bnxt_hwrm_cp_ring_free(bp, txr->tx_cpr);
		bnxt_free_one_tx_ring_skbs(bp, txr, txr->txq_index);
		bnxt_clear_one_cp_ring(bp, txr->tx_cpr);

bnxt_queue_stop() returns 0, so netdev_rx_queue_restart() treats the stop
as successful and calls ndo_queue_mem_free(old_mem). That frees the old
RX descriptor rings and the page_pool buffers the firmware may still be
writing to:

netdev_rx_queue_restart()
  bnxt_queue_stop()
    bnxt_hwrm_rx_ring_free()    <- -EIO, ignored
  ndo_queue_mem_free(old_mem)

This path can be reached from bnxt_tph_update() on an IRQ affinity
change, from devmem dmabuf bind/unbind, or from io_uring zcrx
registration.

Neither this patch nor the later "bnxt_en: refuse to open a device with
stopped DMA" changes these paths. Should they be handled the same way?

>  
>  	/* The completion rings are about to be freed.  After that the
> @@ -7817,6 +7821,19 @@ static void bnxt_hwrm_ring_free(struct bnxt *bp, bool close_path)
>  			bp->grp_info[i].cp_fw_ring_id = INVALID_HW_RING_ID;
>  		}
>  	}
> +
> +	if (!stuck)
> +		return 0;

[Severity: High]
Only the TX, RX and RX AGG frees are counted in stuck. Can the firmware
still own completion or NQ ring memory when this returns 0?

bnxt_hwrm_cp_ring_free() still returns void, and it drops the result for
the L2_CMPL rings in cp_ring_arr:

bnxt_hwrm_cp_ring_free() {
	...
	hwrm_ring_free_send_msg(bp, ring, RING_FREE_REQ_RING_TYPE_L2_CMPL,
				INVALID_HW_RING_ID);
	ring->fw_ring_id = INVALID_HW_RING_ID;
}

The NQ/L2_CMPL free in the loop just above also ignores its return value:

		if (ring->fw_ring_id != INVALID_HW_RING_ID) {
			hwrm_ring_free_send_msg(bp, ring, type,
						INVALID_HW_RING_ID);
			ring->fw_ring_id = INVALID_HW_RING_ID;

bnxt_hwrm_stat_ctx_free() has the same problem. It ignores each
hwrm_req_send() result and sets hw_stats_ctx_id to INVALID_STATS_CTX_ID
anyway, and bnxt_hwrm_resource_free() takes rc only from
bnxt_hwrm_ring_free().

The commit message says only a subset of RING_FREEs time out. Suppose
that subset is only CP/NQ rings or STAT_CTX_FREE. Then bnxt_stop_dma() is
skipped, and __bnxt_close_nic()/bnxt_half_close_nic() go on to
bnxt_free_mem(). That frees the completion/NQ descriptor pages through
bnxt_free_cp_rings() and bnxt_free_all_cp_arrays(). With irq_re_init it
also frees the hw_stats DMA buffers through bnxt_free_ring_stats().

The device writes to these buffers even with no traffic. It writes
completion and NQ entries, async events on cp ring 0 (registered by
bnxt_hwrm_set_async_event_cr() in bnxt_hwrm_ring_alloc()) and periodic
stats.

Doesn't that leave the IO_PAGE_FAULT case from the commit message still
reachable? The later "bnxt_en: refuse to open a device with stopped DMA"
does not change this.

> +
> +	netdev_err(bp->dev, "Firmware did not free %d ring(s)\n", stuck);
> +	return -EIO;
> +}
> +
> +static void bnxt_stop_dma(struct bnxt *bp)
> +{
> +	netdev_err(bp->dev,
> +		   "Disabling DMA before releasing ring memory, the driver must be rebound to recover\n");
> +	pci_clear_master(bp->pdev);

[Severity: Medium]
The commit message says:

  "bnxt_hwrm_resource_free() still frees the remaining firmware
  resources before returning the error, so the shutdown can send every
  message it needs to before the device is stopped."

Is that accurate? The firmware DMA-writes HWRM responses into host
memory:

__hwrm_req_init() {
	...
	ctx->req->resp_addr = cpu_to_le64(dma_handle + BNXT_HWRM_RESP_OFFSET);
	...
}

Once bus mastering is cleared, every later HWRM can only time out, and
bnxt_close() still sends messages after the close:

bnxt_close() {
	...
	bnxt_close_nic(bp, true, true);
	bnxt_hwrm_shutdown_link(bp);
	bnxt_hwrm_if_change(bp, false);
	...
}

bnxt_fw_reset_close() likewise sends HWRM_FUNC_DRV_UNRGTR through
bnxt_hwrm_func_drv_unrgtr() after __bnxt_close_nic().

pci_clear_master() also acts on the whole PCI function, which the bnxt_re
auxiliary device shares. The normal close and TX timeout reset paths
(bnxt_close(), and bnxt_reset() -> bnxt_reset_task()) never call
bnxt_ulp_stop().

The driver's existing paths that disable the device all call
bnxt_ulp_stop() first: bnxt_fw_exception()/bnxt_fw_reset() before
bnxt_fw_reset_close(), bnxt_io_error_detected() and bnxt_suspend().

Would bnxt_re lose DMA, MSI-X delivery and its HWRM channel here without
being notified?

A separate concern, an immediate reopen through bnxt_reset_task() ->
bnxt_open_nic(), is fixed later in the series by "bnxt_en: refuse to
open a device with stopped DMA". The ULP and post-stop HWRM issues are
still present at the end of the series.

[Severity: Medium]
Can bus mastering be turned back on while the firmware still has the
stuck rings? No caller of pci_set_master() in the driver checks for the
stopped state first. For example:

bnxt_resume() {
	...
	pci_set_master(bp->pdev);
	if (bnxt_hwrm_ver_get(bp)) {
		rc = -ENODEV;
		goto resume_exit;
	}
	rc = bnxt_hwrm_func_reset(bp);
	if (rc) {
		rc = -EBUSY;
		goto resume_exit;
	}
	...
}

VER_GET or FUNC_RESET may well fail if the firmware has already stopped
answering RING_FREE. Resume then exits with bus mastering on and nothing
reset.

If the function kept its ring context across suspend (D3hot without an
internal reset), could the stuck RX rings then DMA into pages that
bnxt_free_mem() has already released?

bnxt_fw_reset_task() also calls pci_set_master() unconditionally in its
ENABLE_DEV state.

The later "bnxt_en: refuse to open a device with stopped DMA" only blocks
bnxt_open*(). So "the driver must be rebound to recover" holds for open,
but not for bus mastering being re-enabled.

>  }
>  
>  static int __bnxt_trim_rings(struct bnxt *bp, int *rx, int *tx, int max,

[ ... ]

> @@ -13540,7 +13561,8 @@ static void __bnxt_close_nic(struct bnxt *bp, bool irq_re_init,
>  	if (BNXT_SUPPORTS_MULTI_RSS_CTX(bp))
>  		bnxt_clear_rss_ctxs(bp);
>  	/* Flush rings and disable interrupts */
> -	bnxt_shutdown_nic(bp, irq_re_init);
> +	if (bnxt_shutdown_nic(bp, irq_re_init))
> +		bnxt_stop_dma(bp);

[Severity: Medium]
What happens when the only reason these RING_FREEs fail is a firmware
reset in progress?

NAPI and bnxt_timer are still running here, because bnxt_disable_napi()
and timer_delete_sync() come after bnxt_shutdown_nic(). The async
RESET_NOTIFY handler can set BNXT_STATE_FW_FATAL_COND after
hwrm_ring_free_send_msg() has passed its BNXT_NO_FW_ACCESS() check.
__hwrm_send() then returns -EBUSY.

__hwrm_send() also abandons the wait if the firmware health register
reports not healthy:

__hwrm_send() {
	...
				if (hwrm_wait_must_abort(bp, req_type, &sts)) {
					hwrm_err(bp, ctx, "Resp cmpl intr abandoning msg: 0x%x due to firmware status: 0x%x\n",
						 req_type, sts);
					goto exit;
	...
}

bnxt_close_nic() already notes that "Some firmware messages may fail as
we proceed to close" when BNXT_STATE_IN_FW_RESET is set. With this
patch, each such failure becomes -EIO, stuck++ and then bnxt_stop_dma().

That includes the FW reset close path itself:

bnxt_fw_reset_task()
  bnxt_fw_reset_close()
    __bnxt_close_nic()
      bnxt_shutdown_nic()    <- -EIO
      bnxt_stop_dma()

After the later "bnxt_en: refuse to open a device with stopped DMA",
bnxt_stop_dma() also sets BNXT_STATE_DMA_STOPPED, and nothing clears it.

The reset still re-enables bus mastering in ENABLE_DEV. But in the
OPENING state, bnxt_open() then fails in bnxt_check_dma_stopped() with
-ENODEV, and bnxt_fw_reset_abort() runs.

If a close races with a reset, bnxt_close_nic() sets ABORT_ERR. A later
bnxt_open() is then refused before it reaches bnxt_reinit_after_abort(),
which doesn't call pci_set_master() anyway.

Before this patch a coordinated FW reset could recover the device. Is it
intended that a rebind is now the only way to recover?

>  
>  	/* TODO CHIMP_FW: Link/PHY related cleanup if (link_re_init) */
>

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261106191423.4194081-1-joe%40dama.to

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA
  2026-11-06 19:14 ` [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA Joe Damato
@ 2026-11-07 19:58   ` netdev-bot+sashiko
  0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-11-07 19:58 UTC (permalink / raw)
  To: joe
  Cc: netdev, michael.chan, pavan.chebbi, andrew+netdev, davem,
	edumazet, kuba, pabeni, prashant, edumazet, horms, linux-kernel

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] This patch blocks driver recovery after a full device reset.
- [Medium] bnxt_en: missing BNXT_STATE_DMA_STOPPED check in
  BNXT_NO_FW_ACCESS()

--

bnxt_en: refuse to open a device with stopped DMA

This adds a BNXT_STATE_DMA_STOPPED bit. bnxt_stop_dma() sets it after
the firmware fails to free rings and bus mastering is cleared. While the
bit is set, bnxt_open(), bnxt_open_nic() and bnxt_half_open_nic() refuse
to open the device, so the user has to rebind the driver.

> diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> index b5ef86bf92bf5..eb885fd74064f 100644
> --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> @@ -7833,9 +7833,20 @@ static void bnxt_stop_dma(struct bnxt *bp)
>  {
>  	netdev_err(bp->dev,
>  		   "Disabling DMA before releasing ring memory, the driver must be rebound to recover\n");
> +	set_bit(BNXT_STATE_DMA_STOPPED, &bp->state);
>  	pci_clear_master(bp->pdev);
>  }

[Severity: Medium]
Once bnxt_stop_dma() has cleared bus mastering, can any later HWRM
command complete? The firmware writes HWRM responses into host memory
by DMA. With BNXT_FW_CAP_SHORT_CMD it also fetches the request by DMA.
With the master bit cleared, __hwrm_send() should just poll until
each command times out.

BNXT_NO_FW_ACCESS() only tests BNXT_STATE_FW_FATAL_COND and
pci_channel_offline(). The new BNXT_STATE_DMA_STOPPED bit is never
consulted there, so nothing stops these commands from being sent.

After the failed ring free, __bnxt_close_nic() continues into
bnxt_free_mem(), which calls bnxt_free_ntp_fltrs() and
bnxt_free_l2_filters(). bnxt_close() then sends
bnxt_hwrm_shutdown_link() and bnxt_hwrm_if_change(). The netif_close()
teardown after a failed bnxt_open_nic() goes through the same path.

Suppose the user follows the log message and rebinds the driver.
bnxt_remove_one() still sends bnxt_hwrm_func_drv_unrgtr() and the rest
of the teardown commands, and each of them waits for its full timeout
too.

Most of this runs under rtnl_lock and the netdev instance lock. Every
stalled command lengthens how long those locks are held. Close and
unbind of a device that has already failed can therefore take a long
time.

Should BNXT_NO_FW_ACCESS() also test BNXT_STATE_DMA_STOPPED, so that
__hwrm_send() fails these commands immediately? If so, the bit would
have to be cleared wherever bus mastering is turned back on:
bnxt_fw_reset_task(), bnxt_resume() and bnxt_io_slot_reset().
Otherwise the HWRM commands those paths send right after
pci_set_master() would be refused as well.

[ ... ]

> @@ -13505,6 +13521,10 @@ static int bnxt_open(struct net_device *dev)
>  	struct bnxt *bp = netdev_priv(dev);
>  	int rc;
>  
> +	rc = bnxt_check_dma_stopped(bp);
> +	if (rc)
> +		return rc;
> +
>  	if (test_bit(BNXT_STATE_ABORT_ERR, &bp->state)) {
>  		rc = bnxt_reinit_after_abort(bp);
>  		if (rc) {

[Severity: Medium]
Nothing in the driver clears BNXT_STATE_DMA_STOPPED. That includes the
paths that fully reset the device and call pci_set_master() again.

This check also runs before bnxt_reinit_after_abort() and
bnxt_hwrm_if_change(). Could that make the firmware reset, resume and AER
recovery paths fail every time?

In a non-fatal firmware reset, bnxt_fw_reset_close()->__bnxt_close_nic()
still sends HWRM_RING_FREE. Ring free is only skipped for FW_FATAL_COND
or when the PCI channel is offline. If ring free fails there:

__bnxt_close_nic()
    if (bnxt_shutdown_nic(bp, irq_re_init))
        bnxt_stop_dma(bp);

bnxt_fw_reset_task() then turns bus mastering back on and waits for the
firmware to come up, but the open is refused:

bnxt_fw_reset_task()
    pci_set_master(bp->pdev);
    ...
    case BNXT_FW_RESET_STATE_OPENING:
        ...
        rc = bnxt_open(bp->dev);
        if (rc) {
            netdev_err(bp->dev, "bnxt_open() failed during FW reset\n");
            bnxt_fw_reset_abort(bp, rc);

Suspend/resume looks similar. bnxt_suspend()->bnxt_close() can set the
bit, and then bnxt_resume() does:

bnxt_resume()
    pci_set_master(bp->pdev);
    if (bnxt_hwrm_ver_get(bp)) {
    ...
    rc = bnxt_hwrm_func_reset(bp);
    ...
    if (netif_running(dev)) {
        rc = bnxt_open(dev);

A rebind runs the same pci_set_master() plus HWRM_FUNC_RESET sequence, in
bnxt_init_board() and bnxt_fw_init_one_p1(). Even so, bnxt_open() fails
here and the netdev stays detached.

AER has the same problem. Suppose an earlier failed close left the bit
set. bnxt_io_slot_reset() hard resets the slot, calls pci_set_master()
and then bnxt_hwrm_func_reset(). After that, bnxt_io_resume()->bnxt_open()
and any later ifup are still refused.

With only the preceding commit "bnxt_en: stop DMA before releasing rings
the firmware did not free", these paths turned bus mastering back on and
bnxt_open() succeeded.

The fatal firmware error path already handles a similar case.
bnxt_fw_fatal_close() calls pci_disable_device(), and the reset task
later calls pci_set_master() and reopens the device.

The commit message says "the device needs manual intervention by the
user to become usable again". Does that still hold once the firmware or
the function has been reset?

Should the bit be cleared, or the check skipped, after a firmware reset,
HWRM_FUNC_RESET or slot reset completes?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261106191423.4194081-1-joe%40dama.to

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-11-07 19:58 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 19:14 [PATCH net v5 0/4] bnxt_en: Make RING FREE more robust Joe Damato
2026-11-06 19:14 ` [PATCH net v5 1/4] bnxt_en: return the RING_FREE status to callers Joe Damato
2026-11-07 19:58   ` netdev-bot+sashiko
2026-11-06 19:14 ` [PATCH net v5 2/4] bnxt_en: check HWRM response if completion never arrives Joe Damato
2026-11-07 19:58   ` netdev-bot+sashiko
2026-11-06 19:14 ` [PATCH net v5 3/4] bnxt_en: stop DMA before releasing rings the firmware did not free Joe Damato
2026-11-07 19:58   ` netdev-bot+sashiko
2026-11-06 19:14 ` [PATCH net v5 4/4] bnxt_en: refuse to open a device with stopped DMA Joe Damato
2026-11-07 19:58   ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®