When dynamic MSI-X is supported, bnxt_check_rings() grows the MSI-X vectors with bnxt_change_msix() once FW says the new rings would fit, and keeps them. FW only reserves the extra IRQs later, when bnxt_reserve_rings() calls __bnxt_reserve_rings(). The MSI-X table entries beyond the function's reserved IRQs are not backed until then: the MSI message written when the new vectors are activated is lost, and the entries read back as zero. After FW reserves them they are live but still zero, so the NQ rings bound to them signal address 0 and every completion on those rings is lost. The next ring change (for example ethtool -G) then times out freeing each of those rings and the TX watchdog fires: bnxt_en 0002:01:00.0 eth0: Resp cmpl intr err msg: 0x51 bnxt_en 0002:01:00.0 eth0: hwrm_ring_free type 1 failed. rc:fffffff0 err:0 ... bnxt_en 0002:01:00.0 eth0: NETDEV WATCHDOG: CPU: 17: transmit queue 18 timed out 5716 ms On x86 this is hidden: GENERIC_IRQ_RESERVATION_MODE makes the PCI MSI domains MSI_FLAG_MUST_REACTIVATE, so the message is composed and written again when the IRQ is requested, after FW has reserved the vector. Since commit 0d6a7c417983 ("irqchip/gic-v3-its: Enable dynamic MSI-X allocation"), arm64 GICv3 systems take the dynamic path as well, but write the message only once, at allocation. On an NVIDIA Grace system with a BCM57508, raising the channel count from 18 to 32 left MSI-X entries 18-31 all zero, and the 14 rings using them lost their interrupts. Keep the allocation in bnxt_check_rings() as a check that enough vectors are available, but free the new vectors again right away when FW manages IRQ reservations (BNXT_NEW_RM). bnxt_reserve_rings() then allocates them after __bnxt_reserve_rings() has reserved them. Without BNXT_NEW_RM there is no IRQ reservation and bnxt_reserve_rings() does not grow the vectors, so keep the current behaviour there. In bnxt_reserve_rings(), size that allocation from the rings FW actually reserved (bnxt_get_num_msix()) instead of the count computed before the reservation, so that a partial grant does not activate unreserved entries either, at least while no RoCE driver is registered. The allocation can now fail after FW has committed the reservation. A later open would then find nothing to reserve and return before growing the vectors, and bnxt_request_irq() would use irq_tbl[] entries past bp->total_irqs, which still hold the freed virqs. Grow the vectors in that case as well, and clear irq_tbl[].vector when trimming. Fixes: 2d51eb0bd81c ("bnxt_en: Add MSIX check in bnxt_check_rings()") Reported-by: Andrew Preece Closes: https://lore.kernel.org/all/20261022193620.2152038-1-vlad.wing@gmail.com/ Signed-off-by: Vlad Poenaru --- Changes in v2: - Size the bnxt_reserve_rings() allocation from what FW reserved (bnxt_get_num_msix()), so a partial grant does not activate unreserved entries while no RoCE driver is registered (Sashiko). With RoCE registered, __bnxt_reserve_rings() can still keep more L2 rings than the grant covers; that accounting predates this patch. - Grow the vectors in bnxt_reserve_rings() even when no reservation is needed, and clear irq_tbl[].vector on trim, so a failed allocation after the FW reservation no longer leaves stale virqs behind (Sashiko). - Only free the probed vectors when BNXT_NEW_RM is set; reword the comment. v1: https://lore.kernel.org/all/20261023121321.2127263-1-vlad.wing@gmail.com/ Tested on an NVIDIA Grace system (GICv3 ITS, ACPI) with a BCM57508, FW 230.0.165.3: the boot-time channel change (18 -> 32) followed by a ring resize no longer loses completions. With instrumentation, MSI-X entries 18-31 read back as all zero after bnxt_check_rings() allocated them, and read back correctly once bnxt_reserve_rings() allocated them after the FW reservation. This conflicts with the "bnxt_en: Ring accounting fixes" series on net-next, which also changes the dynamic MSI-X path in bnxt_reserve_rings(). drivers/net/ethernet/broadcom/bnxt/bnxt.c | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 0682060b048ce..55b11c7684313 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11524,6 +11524,7 @@ static int bnxt_change_msix(struct bnxt *bp, int total) map.index = i - 1; map.virq = bp->irq_tbl[i - 1].vector; pci_msix_free_irq(bp->pdev, map); + bp->irq_tbl[i - 1].vector = 0; bp->total_irqs--; } return bp->total_irqs; @@ -11701,8 +11702,23 @@ int bnxt_reserve_rings(struct bnxt *bp, bool irq_re_init) int irqs_required; int rc; - if (!bnxt_need_reserve_rings(bp)) + if (!bnxt_need_reserve_rings(bp)) { + /* FW may already hold the reservation from an earlier + * reservation that was not followed by growing the dynamic + * MSI-X vectors, e.g. because the allocation failed. + */ + if (irq_re_init && BNXT_NEW_RM(bp) && bp->irq_tbl && + pci_msix_can_alloc_dyn(bp->pdev)) { + irqs_required = bnxt_get_num_msix(bp); + if (irqs_required > bp->total_irqs && + bnxt_change_msix(bp, irqs_required) != irqs_required) { + netdev_err(bp->dev, "Unable to allocate %d MSIX vectors, maximum available %d\n", + irqs_required, bp->total_irqs); + return -ENOSPC; + } + } return 0; + } if (BNXT_NEW_RM(bp) && !bnxt_ulp_registered(edev)) { int ulp_msix = bnxt_get_avail_msix(bp, bp->ulp_num_msix_want); @@ -11728,6 +11744,10 @@ int bnxt_reserve_rings(struct bnxt *bp, bool irq_re_init) rc = bnxt_init_int_mode(bp); bnxt_ulp_irq_restart(bp, rc); } else if (irq_change && !rc) { + /* Size the vectors from what FW actually reserved, which + * can be less than what was requested. + */ + irqs_required = bnxt_get_num_msix(bp); if (bnxt_change_msix(bp, irqs_required) != irqs_required) rc = -ENOSPC; } @@ -15093,6 +15113,7 @@ int bnxt_check_rings(struct bnxt *bp, int tx, int rx, bool sh, int tcs, hwr.cp = min_t(int, hwr.cp, bnxt_get_max_func_irqs(bp)); } if (hwr.cp > bp->total_irqs) { + int old_total = bp->total_irqs; int total_msix = bnxt_change_msix(bp, hwr.cp); if (total_msix < hwr.cp) { @@ -15100,6 +15121,17 @@ int bnxt_check_rings(struct bnxt *bp, int tx, int rx, bool sh, int tcs, hwr.cp, total_msix); rc = -ENOSPC; } + /* FW has not reserved the new vectors yet, and writes to + * MSI-X table entries beyond the reserved IRQs don't + * stick (seen on BCM57508). Free them again; + * bnxt_reserve_rings() allocates them after + * __bnxt_reserve_rings(). Without the new resource + * manager there is no IRQ reservation and + * bnxt_reserve_rings() does not grow the vectors, so + * keep them. + */ + if (BNXT_NEW_RM(bp)) + bnxt_change_msix(bp, old_total); } } return rc;