[PATCH] net: enetc: fix sirq-storm by clearing IDR registers

From: Zefir Kurtisi

Date: Fri Feb 20 2026 - 08:31:19 EST


From: Zefir Kurtisi <zefir.kurtisi@xxxxxxxxxxxx>

The fsl_enetc driver experiences soft-IRQ storms on LS1028A systems
where up to 500k interrupts/sec are generated, completely saturating
one CPU core. When running with a single core, this causes watchdog
timeouts and system reboots.

Root cause:
The driver was writing to SITXIDR/SIRXIDR (Station Interface summary
registers) to acknowledge interrupts, but these are W1C registers that
only provide a summary view. According to the LS1028A Reference Manual
(Rev. 0, Chapter 16.3):

- TBaIDR/RBaIDR (per-ring, offset 0xa4): RO, "Reading will
automatically clear all events"
- SITXIDR/SIRXIDR (summary, offset 0xa18/0xa28): W1C, "provides a
non-destructive read access"

The actual interrupt sources are the per-ring TBaIDR/RBaIDR registers.
The summary registers merely reflect their combined state. Writing to
SITXIDR/SIRXIDR does not clear the underlying per-ring sources, causing
the hardware to immediately re-assert the interrupt.

Fix:
1. Point ring->idr to per-ring TBaIDR/RBaIDR instead of summary
registers
2. Remove per-packet writes to SITXIDR/SIRXIDR from packet processing
3. Read TBaIDR/RBaIDR once per NAPI poll (in enetc_poll) before
re-enabling interrupts

This properly acknowledges interrupts at the hardware level and
eliminates the interrupt storm. The optimization of clearing once per
NAPI poll rather than per packet also reduces register access overhead.

Fixes: d4fd0404c1c9 ("enetc: Introduce basic PF and VF ENETC ethernet drivers")
Tested-on: LS1028A (NXP Layerscape), Linux 6.6.93
Signed-off-by: Zefir Kurtisi <zefir.kurtisi@xxxxxxxxxxxx>
---
drivers/net/ethernet/freescale/enetc/enetc.c | 18 +++++++++---------
drivers/net/ethernet/freescale/enetc/enetc.h | 1 +
2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c
index e380a4f39855..8442e87b9b86 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc.c
@@ -1291,9 +1291,6 @@ static bool enetc_clean_tx_ring(struct enetc_bdr *tx_ring, int napi_budget)
/* BD iteration loop end */
if (is_eof) {
tx_frm_cnt++;
- /* re-arm interrupt source */
- enetc_wr_reg_hot(tx_ring->idr, BIT(tx_ring->index) |
- BIT(16 + tx_ring->index));
}

if (unlikely(!bds_to_clean))
@@ -1620,7 +1617,6 @@ static int enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
if (!bd_status)
break;

- enetc_wr_reg_hot(rx_ring->idr, BIT(rx_ring->index));
dma_rmb(); /* for reading other rxbd fields */

if (enetc_check_bd_errors_and_consume(rx_ring, bd_status,
@@ -1977,7 +1973,6 @@ static int enetc_clean_rx_ring_xdp(struct enetc_bdr *rx_ring,
if (!bd_status)
break;

- enetc_wr_reg_hot(rx_ring->idr, BIT(rx_ring->index));
dma_rmb(); /* for reading other rxbd fields */

if (enetc_check_bd_errors_and_consume(rx_ring, bd_status,
@@ -2143,12 +2138,16 @@ static int enetc_poll(struct napi_struct *napi, int budget)
v->rx_napi_work = false;

enetc_lock_mdio();
- /* enable interrupts */
+ /* Read RBaIDR to acknowledge interrupt (RO, read-to-clear) */
+ enetc_rd_reg_hot(rx_ring->idr);
enetc_wr_reg_hot(v->rbier, ENETC_RBIER_RXTIE);

- for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
+ for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS) {
+ /* Read TBaIDR to acknowledge interrupt (RO, read-to-clear) */
+ enetc_rd_reg_hot(v->tbidr_base + ENETC_BDR_OFF(i));
enetc_wr_reg_hot(v->tbier_base + ENETC_BDR_OFF(i),
ENETC_TBIER_TXTIE);
+ }

enetc_unlock_mdio();

@@ -2608,7 +2607,7 @@ static void enetc_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)

tx_ring->tpir = hw->reg + ENETC_BDR(TX, idx, ENETC_TBPIR);
tx_ring->tcir = hw->reg + ENETC_BDR(TX, idx, ENETC_TBCIR);
- tx_ring->idr = hw->reg + ENETC_SITXIDR;
+ tx_ring->idr = hw->reg + ENETC_BDR(TX, idx, ENETC_TBIDR);
}

static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring,
@@ -2650,7 +2649,7 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring,
rbmr |= ENETC_RBMR_VTE;

rx_ring->rcir = hw->reg + ENETC_BDR(RX, idx, ENETC_RBCIR);
- rx_ring->idr = hw->reg + ENETC_SIRXIDR;
+ rx_ring->idr = hw->reg + ENETC_BDR(RX, idx, ENETC_RBIDR);

rx_ring->next_to_clean = 0;
rx_ring->next_to_use = 0;
@@ -2793,6 +2792,7 @@ static int enetc_setup_irqs(struct enetc_ndev_priv *priv)
}

v->tbier_base = hw->reg + ENETC_BDR(TX, 0, ENETC_TBIER);
+ v->tbidr_base = hw->reg + ENETC_BDR(TX, 0, ENETC_TBIDR);
v->rbier = hw->reg + ENETC_BDR(RX, i, ENETC_RBIER);
v->ricr1 = hw->reg + ENETC_BDR(RX, i, ENETC_RBICR1);

diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h
index aecd40aeef9c..2b4b052e43c8 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc.h
@@ -374,6 +374,7 @@ static inline bool enetc_is_pseudo_mac(struct enetc_si *si)
struct enetc_int_vector {
void __iomem *rbier;
void __iomem *tbier_base;
+ void __iomem *tbidr_base;
void __iomem *ricr1;
unsigned long tx_rings_map;
int count_tx_rings;
--
2.43.0