* [PATCH] net: airoha: fix MIB stats collection to be lossless
@ 2026-06-30 11:18 Aniket Negi
2026-06-30 14:21 ` Lorenzo Bianconi
2026-07-01 17:39 ` [PATCH net v2] " Aniket Negi
0 siblings, 2 replies; 7+ messages in thread
From: Aniket Negi @ 2026-06-30 11:18 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Christian Marangi, Simon Horman, linux-arm-kernel,
linux-mediatek, netdev, linux-kernel, Aniket Negi
The airoha_dev_get_hw_stats() function had two correctness issues in the
way it collects hardware MIB counters.
Bug 1: Read-clear race causes silent packet loss in statistics
airoha_update_hw_stats() read all MIB registers and then cleared them
via REG_FE_GDM_MIB_CLEAR. There is a time window between the last
register read and the hardware clear. Any packet that the hardware
counts during this window is lost: the register is incremented, then
cleared, without the increment ever being read by software. Under
sustained traffic this causes a permanent and growing undercount in all
reported statistics.
This is particularly misleading for tx_ok_pkts and tx_ok_bytes, which
routers and traffic monitors use to detect packet forwarding loss
between two points in a hardware-accelerated path (e.g., between two
netdevs in the QDMA/PPE fast-path). An inaccurate count makes it
impossible to reliably attribute drops in the forwarding pipeline
without capturing traffic at both ends independently.
Bug 2: 32-bit counter overflow causes stat corruption
Several MIB registers are only 32 bits wide: tx_drops, tx_broadcast,
tx_multicast, rx_drops, rx_broadcast, rx_multicast, rx_errors,
rx_crc_error, rx_over_errors, rx_fragment, rx_jabber, and the runt and
long buckets of the tx_len[]/rx_len[].
The original code relied on MIB_CLEAR to keep register values small
enough that a simple '+= val' per cycle did not lose data across a
wrap. Once clearing is removed (to fix Bug 1), raw '+= val' silently
corrupts the accumulated software counter on overflow.
Fix both issues together:
- 64-bit H+L register pairs (tx_ok_pkts, tx_ok_bytes, tx_len[1..5],
rx_ok_pkts, rx_ok_bytes, rx_len[1..5]): read directly from hardware
without clearing. Hardware accumulates the full running total; a
single direct assignment per poll is correct and lossless.
- 32-bit registers (tx_drops, tx_broadcast, tx_multicast, rx_drops,
rx_broadcast, rx_multicast, rx_errors, rx_crc_error, rx_over_errors,
rx_fragment, rx_jabber, and the runt/long buckets in tx_len[0]/[6]
and rx_len[0]/[6]): track the previous hardware value in a new
hw_prev_stats sub-struct inside airoha_hw_stats and accumulate
(u32)(curr - prev) into the 64-bit software counter. Unsigned
subtraction handles wrap-around transparently:
prev=0xFFFFFF00, curr=0x00000010 -> delta=(u32)(0x10-0xFFFFFF00)=0x110
Remove the REG_FE_GDM_MIB_CLEAR write from airoha_update_hw_stats()
entirely. Because the driver no longer clears hardware counters, the
read-clear race window is eliminated.
The hw_prev_stats fields are zero-initialised by the existing
devm_kzalloc() call in airoha_alloc_gdm_device().
Fixes: 8f4695fb67b2 ("net: airoha: better handle MIBs for GDM ports with multiple devs attached")
Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
---
drivers/net/ethernet/airoha/airoha_eth.c | 132 +++++++++++------------
drivers/net/ethernet/airoha/airoha_eth.h | 22 ++++
2 files changed, 86 insertions(+), 68 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 1caf6766f2c0..7ae4e294478e 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1696,133 +1696,133 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
u64_stats_update_begin(&dev->stats.syncp);
- /* TX */
+ /* TX - 64-bit H+L registers: hw accumulates the total, read directly. */
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
- dev->stats.tx_ok_pkts += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
- dev->stats.tx_ok_pkts += val;
+ dev->stats.tx_ok_pkts = (u64)val << 32;
+ dev->stats.tx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
- dev->stats.tx_ok_bytes += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
- dev->stats.tx_ok_bytes += val;
+ dev->stats.tx_ok_bytes = (u64)val << 32;
+ dev->stats.tx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
+ /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
- dev->stats.tx_drops += val;
+ dev->stats.tx_drops += (u32)(val - dev->stats.hw_prev_stats.tx_drops);
+ dev->stats.hw_prev_stats.tx_drops = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
- dev->stats.tx_broadcast += val;
+ dev->stats.tx_broadcast += (u32)(val - dev->stats.hw_prev_stats.tx_broadcast);
+ dev->stats.hw_prev_stats.tx_broadcast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
- dev->stats.tx_multicast += val;
+ dev->stats.tx_multicast += (u32)(val - dev->stats.hw_prev_stats.tx_multicast);
+ dev->stats.hw_prev_stats.tx_multicast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
- dev->stats.tx_len[i] += val;
+ dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i]);
+ dev->stats.hw_prev_stats.tx_len[i] = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] += (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] = (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] = (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] = (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] = (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] = (u64)val << 32;
+ dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i]);
+ dev->stats.hw_prev_stats.tx_len[i++] = val;
/* RX */
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
- dev->stats.rx_ok_pkts += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
- dev->stats.rx_ok_pkts += val;
+ dev->stats.rx_ok_pkts = (u64)val << 32;
+ dev->stats.rx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
- dev->stats.rx_ok_bytes += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
- dev->stats.rx_ok_bytes += val;
+ dev->stats.rx_ok_bytes = (u64)val << 32;
+ dev->stats.rx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
- dev->stats.rx_drops += val;
+ dev->stats.rx_drops += (u32)(val - dev->stats.hw_prev_stats.rx_drops);
+ dev->stats.hw_prev_stats.rx_drops = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
- dev->stats.rx_broadcast += val;
+ dev->stats.rx_broadcast += (u32)(val - dev->stats.hw_prev_stats.rx_broadcast);
+ dev->stats.hw_prev_stats.rx_broadcast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
- dev->stats.rx_multicast += val;
+ dev->stats.rx_multicast += (u32)(val - dev->stats.hw_prev_stats.rx_multicast);
+ dev->stats.hw_prev_stats.rx_multicast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
- dev->stats.rx_errors += val;
+ dev->stats.rx_errors += (u32)(val - dev->stats.hw_prev_stats.rx_errors);
+ dev->stats.hw_prev_stats.rx_errors = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
- dev->stats.rx_crc_error += val;
+ dev->stats.rx_crc_error += (u32)(val - dev->stats.hw_prev_stats.rx_crc_error);
+ dev->stats.hw_prev_stats.rx_crc_error = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
- dev->stats.rx_over_errors += val;
+ dev->stats.rx_over_errors += (u32)(val - dev->stats.hw_prev_stats.rx_over_errors);
+ dev->stats.hw_prev_stats.rx_over_errors = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
- dev->stats.rx_fragment += val;
+ dev->stats.rx_fragment += (u32)(val - dev->stats.hw_prev_stats.rx_fragment);
+ dev->stats.hw_prev_stats.rx_fragment = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
- dev->stats.rx_jabber += val;
+ dev->stats.rx_jabber += (u32)(val - dev->stats.hw_prev_stats.rx_jabber);
+ dev->stats.hw_prev_stats.rx_jabber = val;
i = 0;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
- dev->stats.rx_len[i] += val;
+ dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i]);
+ dev->stats.hw_prev_stats.rx_len[i] = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] += (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] = (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] = (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] = (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] = (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
- val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] = (u64)val << 32;
+ dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i]);
+ dev->stats.hw_prev_stats.rx_len[i++] = val;
u64_stats_update_end(&dev->stats.syncp);
}
@@ -1839,10 +1839,6 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
airoha_dev_get_hw_stats(port->devs[i]);
}
- /* Reset MIB counters */
- airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
- FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
-
spin_unlock(&port->stats_lock);
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 2765244d937c..af12ad6eac17 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -244,6 +244,28 @@ struct airoha_hw_stats {
u64 rx_fragment;
u64 rx_jabber;
u64 rx_len[7];
+
+ struct {
+ /* Previous HW register values for 32-bit counter delta tracking.
+ * Storing the last seen value and accumulating (u32)(curr - prev)
+ * in 64-bit software counter & handles wrap-around transparently
+ * via unsigned arithmetic. These fields are never reported to
+ * userspace.
+ */
+ u32 tx_drops;
+ u32 tx_broadcast;
+ u32 tx_multicast;
+ u32 tx_len[7];
+ u32 rx_drops;
+ u32 rx_broadcast;
+ u32 rx_multicast;
+ u32 rx_errors;
+ u32 rx_crc_error;
+ u32 rx_over_errors;
+ u32 rx_fragment;
+ u32 rx_jabber;
+ u32 rx_len[7];
+ } hw_prev_stats;
};
enum {
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] net: airoha: fix MIB stats collection to be lossless
2026-06-30 11:18 [PATCH] net: airoha: fix MIB stats collection to be lossless Aniket Negi
@ 2026-06-30 14:21 ` Lorenzo Bianconi
2026-07-01 6:38 ` Aniket Negi
2026-07-01 17:39 ` [PATCH net v2] " Aniket Negi
1 sibling, 1 reply; 7+ messages in thread
From: Lorenzo Bianconi @ 2026-06-30 14:21 UTC (permalink / raw)
To: Aniket Negi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Christian Marangi, Simon Horman, linux-arm-kernel,
linux-mediatek, netdev, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 15019 bytes --]
> The airoha_dev_get_hw_stats() function had two correctness issues in the
> way it collects hardware MIB counters.
>
> Bug 1: Read-clear race causes silent packet loss in statistics
>
> airoha_update_hw_stats() read all MIB registers and then cleared them
> via REG_FE_GDM_MIB_CLEAR. There is a time window between the last
> register read and the hardware clear. Any packet that the hardware
> counts during this window is lost: the register is incremented, then
> cleared, without the increment ever being read by software. Under
> sustained traffic this causes a permanent and growing undercount in all
> reported statistics.
>
> This is particularly misleading for tx_ok_pkts and tx_ok_bytes, which
> routers and traffic monitors use to detect packet forwarding loss
> between two points in a hardware-accelerated path (e.g., between two
> netdevs in the QDMA/PPE fast-path). An inaccurate count makes it
> impossible to reliably attribute drops in the forwarding pipeline
> without capturing traffic at both ends independently.
>
> Bug 2: 32-bit counter overflow causes stat corruption
>
> Several MIB registers are only 32 bits wide: tx_drops, tx_broadcast,
> tx_multicast, rx_drops, rx_broadcast, rx_multicast, rx_errors,
> rx_crc_error, rx_over_errors, rx_fragment, rx_jabber, and the runt and
> long buckets of the tx_len[]/rx_len[].
>
> The original code relied on MIB_CLEAR to keep register values small
> enough that a simple '+= val' per cycle did not lose data across a
> wrap. Once clearing is removed (to fix Bug 1), raw '+= val' silently
> corrupts the accumulated software counter on overflow.
>
> Fix both issues together:
>
> - 64-bit H+L register pairs (tx_ok_pkts, tx_ok_bytes, tx_len[1..5],
> rx_ok_pkts, rx_ok_bytes, rx_len[1..5]): read directly from hardware
> without clearing. Hardware accumulates the full running total; a
> single direct assignment per poll is correct and lossless.
>
> - 32-bit registers (tx_drops, tx_broadcast, tx_multicast, rx_drops,
> rx_broadcast, rx_multicast, rx_errors, rx_crc_error, rx_over_errors,
> rx_fragment, rx_jabber, and the runt/long buckets in tx_len[0]/[6]
> and rx_len[0]/[6]): track the previous hardware value in a new
> hw_prev_stats sub-struct inside airoha_hw_stats and accumulate
> (u32)(curr - prev) into the 64-bit software counter. Unsigned
> subtraction handles wrap-around transparently:
> prev=0xFFFFFF00, curr=0x00000010 -> delta=(u32)(0x10-0xFFFFFF00)=0x110
>
> Remove the REG_FE_GDM_MIB_CLEAR write from airoha_update_hw_stats()
> entirely. Because the driver no longer clears hardware counters, the
> read-clear race window is eliminated.
>
> The hw_prev_stats fields are zero-initialised by the existing
> devm_kzalloc() call in airoha_alloc_gdm_device().
>
> Fixes: 8f4695fb67b2 ("net: airoha: better handle MIBs for GDM ports with multiple devs attached")
> Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 132 +++++++++++------------
> drivers/net/ethernet/airoha/airoha_eth.h | 22 ++++
> 2 files changed, 86 insertions(+), 68 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 1caf6766f2c0..7ae4e294478e 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -1696,133 +1696,133 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
>
> u64_stats_update_begin(&dev->stats.syncp);
>
> - /* TX */
> + /* TX - 64-bit H+L registers: hw accumulates the total, read directly. */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
> - dev->stats.tx_ok_pkts += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> - dev->stats.tx_ok_pkts += val;
> + dev->stats.tx_ok_pkts = (u64)val << 32;
I guess it is more readable to store REG_FE_GDM_TX_OK_PKT_CNT_L() read in val
here. Something like:
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
dev->stats.tx_ok_pkts += val;
This apply even to occurrence below
> + dev->stats.tx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
> - dev->stats.tx_ok_bytes += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
> - dev->stats.tx_ok_bytes += val;
> + dev->stats.tx_ok_bytes = (u64)val << 32;
> + dev->stats.tx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
>
> + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
> - dev->stats.tx_drops += val;
> + dev->stats.tx_drops += (u32)(val - dev->stats.hw_prev_stats.tx_drops);
> + dev->stats.hw_prev_stats.tx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
> - dev->stats.tx_broadcast += val;
> + dev->stats.tx_broadcast += (u32)(val - dev->stats.hw_prev_stats.tx_broadcast);
> + dev->stats.hw_prev_stats.tx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
> - dev->stats.tx_multicast += val;
> + dev->stats.tx_multicast += (u32)(val - dev->stats.hw_prev_stats.tx_multicast);
> + dev->stats.hw_prev_stats.tx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
> - dev->stats.tx_len[i] += val;
> + dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i]);
> + dev->stats.hw_prev_stats.tx_len[i] = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] += (u64)val << 32;
Since now we do not reset MIB counters, this is wrong, you can't use "+="
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] = (u64)val << 32;
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] = (u64)val << 32;
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] = (u64)val << 32;
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] = (u64)val << 32;
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] = (u64)val << 32;
> + dev->stats.tx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i]);
> + dev->stats.hw_prev_stats.tx_len[i++] = val;
>
> /* RX */
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
> - dev->stats.rx_ok_pkts += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
> - dev->stats.rx_ok_pkts += val;
> + dev->stats.rx_ok_pkts = (u64)val << 32;
> + dev->stats.rx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
> - dev->stats.rx_ok_bytes += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
> - dev->stats.rx_ok_bytes += val;
> + dev->stats.rx_ok_bytes = (u64)val << 32;
> + dev->stats.rx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
> - dev->stats.rx_drops += val;
> + dev->stats.rx_drops += (u32)(val - dev->stats.hw_prev_stats.rx_drops);
> + dev->stats.hw_prev_stats.rx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
> - dev->stats.rx_broadcast += val;
> + dev->stats.rx_broadcast += (u32)(val - dev->stats.hw_prev_stats.rx_broadcast);
> + dev->stats.hw_prev_stats.rx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
> - dev->stats.rx_multicast += val;
> + dev->stats.rx_multicast += (u32)(val - dev->stats.hw_prev_stats.rx_multicast);
> + dev->stats.hw_prev_stats.rx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
> - dev->stats.rx_errors += val;
> + dev->stats.rx_errors += (u32)(val - dev->stats.hw_prev_stats.rx_errors);
> + dev->stats.hw_prev_stats.rx_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
> - dev->stats.rx_crc_error += val;
> + dev->stats.rx_crc_error += (u32)(val - dev->stats.hw_prev_stats.rx_crc_error);
> + dev->stats.hw_prev_stats.rx_crc_error = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
> - dev->stats.rx_over_errors += val;
> + dev->stats.rx_over_errors += (u32)(val - dev->stats.hw_prev_stats.rx_over_errors);
> + dev->stats.hw_prev_stats.rx_over_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
> - dev->stats.rx_fragment += val;
> + dev->stats.rx_fragment += (u32)(val - dev->stats.hw_prev_stats.rx_fragment);
> + dev->stats.hw_prev_stats.rx_fragment = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
> - dev->stats.rx_jabber += val;
> + dev->stats.rx_jabber += (u32)(val - dev->stats.hw_prev_stats.rx_jabber);
> + dev->stats.hw_prev_stats.rx_jabber = val;
>
> i = 0;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
> - dev->stats.rx_len[i] += val;
> + dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i]);
> + dev->stats.hw_prev_stats.rx_len[i] = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] += (u64)val << 32;
same here.
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] = (u64)val << 32;
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] = (u64)val << 32;
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] = (u64)val << 32;
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] = (u64)val << 32;
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] = (u64)val << 32;
> + dev->stats.rx_len[i++] += airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i]);
> + dev->stats.hw_prev_stats.rx_len[i++] = val;
>
> u64_stats_update_end(&dev->stats.syncp);
> }
> @@ -1839,10 +1839,6 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> airoha_dev_get_hw_stats(port->devs[i]);
> }
>
> - /* Reset MIB counters */
> - airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
> - FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
> -
> spin_unlock(&port->stats_lock);
> }
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index 2765244d937c..af12ad6eac17 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -244,6 +244,28 @@ struct airoha_hw_stats {
> u64 rx_fragment;
> u64 rx_jabber;
> u64 rx_len[7];
> +
> + struct {
> + /* Previous HW register values for 32-bit counter delta tracking.
> + * Storing the last seen value and accumulating (u32)(curr - prev)
> + * in 64-bit software counter & handles wrap-around transparently
> + * via unsigned arithmetic. These fields are never reported to
> + * userspace.
> + */
can you please align the comment here?
> + u32 tx_drops;
> + u32 tx_broadcast;
> + u32 tx_multicast;
> + u32 tx_len[7];
> + u32 rx_drops;
> + u32 rx_broadcast;
> + u32 rx_multicast;
> + u32 rx_errors;
> + u32 rx_crc_error;
> + u32 rx_over_errors;
> + u32 rx_fragment;
> + u32 rx_jabber;
> + u32 rx_len[7];
> + } hw_prev_stats;
Maybe something like "prev_val32" ?
Regards,
Lorenzo
> };
>
> enum {
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] net: airoha: fix MIB stats collection to be lossless
2026-06-30 14:21 ` Lorenzo Bianconi
@ 2026-07-01 6:38 ` Aniket Negi
2026-07-01 6:51 ` Lorenzo Bianconi
0 siblings, 1 reply; 7+ messages in thread
From: Aniket Negi @ 2026-07-01 6:38 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
linux-kernel, aniket.negi
Hi Lorenzo,
Thank you for the detailed review and suggestions!
> > + /* TX - 64-bit H+L registers: hw accumulates the total, read directly. =
> */
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
> > - dev->stats.tx_ok_pkts += ((u64)val << 32);
> > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> > - dev->stats.tx_ok_pkts += val;
> > + dev->stats.tx_ok_pkts = (u64)val << 32;
>
> I guess it is more readable to store REG_FE_GDM_TX_OK_PKT_CNT_L() read in v=
> al
> here. Something like:
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> dev->stats.tx_ok_pkts += val;
>
> This apply even to occurrence below
Agreed. I'll store CNT_L read in val first to improve readability.
> > + dev->stats.tx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L=
> (port->id));
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
> > - dev->stats.tx_ok_bytes += ((u64)val << 32);
> > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
> > - dev->stats.tx_ok_bytes += val;
> > + dev->stats.tx_ok_bytes = (u64)val << 32;
> > + dev->stats.tx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT=
> _L(port->id));
> > =20
> > + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
> > - dev->stats.tx_drops += val;
> > + dev->stats.tx_drops += (u32)(val - dev->stats.hw_prev_stats.tx_drops);
> > + dev->stats.hw_prev_stats.tx_drops = val;
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
> > - dev->stats.tx_broadcast += val;
> > + dev->stats.tx_broadcast += (u32)(val - dev->stats.hw_prev_stats.tx_br=
> oadcast);
> > + dev->stats.hw_prev_stats.tx_broadcast = val;
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
> > - dev->stats.tx_multicast += val;
> > + dev->stats.tx_multicast += (u32)(val - dev->stats.hw_prev_stats.tx_mu=
> lticast);
> > + dev->stats.hw_prev_stats.tx_multicast = val;
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
> > - dev->stats.tx_len[i] += val;
> > + dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i=
> ]);
> > + dev->stats.hw_prev_stats.tx_len[i] = val;
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
> > - dev->stats.tx_len[i] += ((u64)val << 32);
> > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
> > - dev->stats.tx_len[i++] += val;
> > + dev->stats.tx_len[i] += (u64)val << 32;
>
> Since now we do not reset MIB counters, this is wrong, you can't use "+="
You are absolutely right, since MIB counters are no longer cleared, using "+=" for E64 counter would cause double counting each iteration. This was missed in the patch, specifically for the case where runt count(32 bit) and E64 counter (64 bit) need to be combined in the same counter.
I'll fix this by using separate accumulator fields to "tx_runt_accum/rx_runt_accum" to track the runt deltas, then compute tx_len[i] as tx_len[i]= tx_runt_accum + E64_CNT (H+L).
> > val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
> > - dev->stats.rx_len[i] += val;
> > + dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i=
> ]);
> > + dev->stats.hw_prev_stats.rx_len[i] = val;
> > =20
> > val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
> > - dev->stats.rx_len[i] += ((u64)val << 32);
> > - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
> > - dev->stats.rx_len[i++] += val;
> > + dev->stats.rx_len[i] += (u64)val << 32;
>
> same here.
Acked. The same approach above will be applied to rx_len[i].
> > +
> > + struct {
> > + /* Previous HW register values for 32-bit counter delta tracking.
> > + * Storing the last seen value and accumulating (u32)(curr - prev)
> > + * in 64-bit software counter & handles wrap-around transparently
> > + * via unsigned arithmetic. These fields are never reported to
> > + * userspace.
> > + */
>
> can you please align the comment here?
Will fix the comment alignment.
>
> > + u32 tx_drops;
> > + u32 tx_broadcast;
> > + u32 tx_multicast;
> > + u32 tx_len[7];
> > + u32 rx_drops;
> > + u32 rx_broadcast;
> > + u32 rx_multicast;
> > + u32 rx_errors;
> > + u32 rx_crc_error;
> > + u32 rx_over_errors;
> > + u32 rx_fragment;
> > + u32 rx_jabber;
> > + u32 rx_len[7];
> > + } hw_prev_stats;
>
> Maybe something like "prev_val32" ?
>
> Will update the name of struct to hold prev counter from hw_pre_stats to prev_val32.
Good suggestion. However, since the struct hw_prev_stats now contains both u32 (previous register value) and u64 (runt accumulators) fields. I'll rename it to "prev_mib_state" to better reflect its dual purpose of storing previous register values for delta calculation and accumulators for combined counters.
Regards,
Aniket Negi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] net: airoha: fix MIB stats collection to be lossless
2026-07-01 6:38 ` Aniket Negi
@ 2026-07-01 6:51 ` Lorenzo Bianconi
2026-07-01 10:29 ` Aniket Negi
0 siblings, 1 reply; 7+ messages in thread
From: Lorenzo Bianconi @ 2026-07-01 6:51 UTC (permalink / raw)
To: Aniket Negi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
linux-kernel, aniket.negi
[-- Attachment #1: Type: text/plain, Size: 5633 bytes --]
> Hi Lorenzo,
>
> Thank you for the detailed review and suggestions!
>
> > > + /* TX - 64-bit H+L registers: hw accumulates the total, read directly. =
> > */
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
> > > - dev->stats.tx_ok_pkts += ((u64)val << 32);
> > > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> > > - dev->stats.tx_ok_pkts += val;
> > > + dev->stats.tx_ok_pkts = (u64)val << 32;
> >
> > I guess it is more readable to store REG_FE_GDM_TX_OK_PKT_CNT_L() read in v=
> > al
> > here. Something like:
> >
> > val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> > dev->stats.tx_ok_pkts += val;
> >
> > This apply even to occurrence below
> Agreed. I'll store CNT_L read in val first to improve readability.
>
> > > + dev->stats.tx_ok_pkts += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L=
> > (port->id));
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
> > > - dev->stats.tx_ok_bytes += ((u64)val << 32);
> > > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
> > > - dev->stats.tx_ok_bytes += val;
> > > + dev->stats.tx_ok_bytes = (u64)val << 32;
> > > + dev->stats.tx_ok_bytes += airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT=
> > _L(port->id));
> > > =20
> > > + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
> > > - dev->stats.tx_drops += val;
> > > + dev->stats.tx_drops += (u32)(val - dev->stats.hw_prev_stats.tx_drops);
> > > + dev->stats.hw_prev_stats.tx_drops = val;
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
> > > - dev->stats.tx_broadcast += val;
> > > + dev->stats.tx_broadcast += (u32)(val - dev->stats.hw_prev_stats.tx_br=
> > oadcast);
> > > + dev->stats.hw_prev_stats.tx_broadcast = val;
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
> > > - dev->stats.tx_multicast += val;
> > > + dev->stats.tx_multicast += (u32)(val - dev->stats.hw_prev_stats.tx_mu=
> > lticast);
> > > + dev->stats.hw_prev_stats.tx_multicast = val;
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
> > > - dev->stats.tx_len[i] += val;
> > > + dev->stats.tx_len[i] += (u32)(val - dev->stats.hw_prev_stats.tx_len[i=
> > ]);
> > > + dev->stats.hw_prev_stats.tx_len[i] = val;
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
> > > - dev->stats.tx_len[i] += ((u64)val << 32);
> > > - val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
> > > - dev->stats.tx_len[i++] += val;
> > > + dev->stats.tx_len[i] += (u64)val << 32;
> >
> > Since now we do not reset MIB counters, this is wrong, you can't use "+="
>
> You are absolutely right, since MIB counters are no longer cleared, using "+=" for E64 counter would cause double counting each iteration. This was missed in the patch, specifically for the case where runt count(32 bit) and E64 counter (64 bit) need to be combined in the same counter.
>
> I'll fix this by using separate accumulator fields to "tx_runt_accum/rx_runt_accum" to track the runt deltas, then compute tx_len[i] as tx_len[i]= tx_runt_accum + E64_CNT (H+L).
>
> > > val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
> > > - dev->stats.rx_len[i] += val;
> > > + dev->stats.rx_len[i] += (u32)(val - dev->stats.hw_prev_stats.rx_len[i=
> > ]);
> > > + dev->stats.hw_prev_stats.rx_len[i] = val;
> > > =20
> > > val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
> > > - dev->stats.rx_len[i] += ((u64)val << 32);
> > > - val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
> > > - dev->stats.rx_len[i++] += val;
> > > + dev->stats.rx_len[i] += (u64)val << 32;
> >
> > same here.
>
> Acked. The same approach above will be applied to rx_len[i].
>
> > > +
> > > + struct {
> > > + /* Previous HW register values for 32-bit counter delta tracking.
> > > + * Storing the last seen value and accumulating (u32)(curr - prev)
> > > + * in 64-bit software counter & handles wrap-around transparently
> > > + * via unsigned arithmetic. These fields are never reported to
> > > + * userspace.
> > > + */
> >
> > can you please align the comment here?
>
> Will fix the comment alignment.
>
> >
> > > + u32 tx_drops;
> > > + u32 tx_broadcast;
> > > + u32 tx_multicast;
> > > + u32 tx_len[7];
> > > + u32 rx_drops;
> > > + u32 rx_broadcast;
> > > + u32 rx_multicast;
> > > + u32 rx_errors;
> > > + u32 rx_crc_error;
> > > + u32 rx_over_errors;
> > > + u32 rx_fragment;
> > > + u32 rx_jabber;
> > > + u32 rx_len[7];
> > > + } hw_prev_stats;
> >
> > Maybe something like "prev_val32" ?
> >
> > Will update the name of struct to hold prev counter from hw_pre_stats to prev_val32.
>
> Good suggestion. However, since the struct hw_prev_stats now contains both u32 (previous register value) and u64 (runt accumulators) fields. I'll rename it to "prev_mib_state" to better reflect its dual purpose of storing previous register values for delta calculation and accumulators for combined counters.
Maybe better mib_prev?
Since now we do not reset the MIB counters in airoha_update_hw_stats(), we can
get rid of the for loop there and just call airoha_dev_get_hw_stats() with the
provided dev pointer. Even better, just rename airoha_dev_get_hw_stats() in
airoha_update_hw_stats() and move the spinlock there. What do you think?
Regards,
Lorenzo
>
> Regards,
> Aniket Negi
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] net: airoha: fix MIB stats collection to be lossless
2026-07-01 6:51 ` Lorenzo Bianconi
@ 2026-07-01 10:29 ` Aniket Negi
0 siblings, 0 replies; 7+ messages in thread
From: Aniket Negi @ 2026-07-01 10:29 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
linux-kernel, aniket.negi
> Maybe better mib_prev?
Ok sure will update. mib_prev is cleaner and more concise.
> Since now we do not reset the MIB counters in airoha_update_hw_stats(), we can
> get rid of the for loop there and just call airoha_dev_get_hw_stats() with the
> provided dev pointer. Even better, just rename airoha_dev_get_hw_stats() in
> airoha_update_hw_stats() and move the spinlock there. What do you think?
I am aligned with your suggestion, Since after removing MIB reset logic, there isn't need to iterate over all devs on airoha_gdm_port. This will simplify the code significantly.
I'll refactor as follows:
1. Remove the for loop in airoha_update_hw_stats()
2. Rename airoha_dev_get_hw_stats() to airoha_update_hw_stats()
3) move the spinlock into the renamed function.
Best Regards,
Aniket Negi
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v2] net: airoha: fix MIB stats collection to be lossless
2026-06-30 11:18 [PATCH] net: airoha: fix MIB stats collection to be lossless Aniket Negi
2026-06-30 14:21 ` Lorenzo Bianconi
@ 2026-07-01 17:39 ` Aniket Negi
2026-07-01 22:43 ` Lorenzo Bianconi
1 sibling, 1 reply; 7+ messages in thread
From: Aniket Negi @ 2026-07-01 17:39 UTC (permalink / raw)
To: lorenzo, netdev
Cc: Aniket Negi, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
linux-kernel
The current driver resets hardware MIB counters after every read via
REG_FE_GDM_MIB_CLEAR. This creates a race window: packets arriving
between the read and the clear are silently lost from statistics.
Fix this by removing the MIB clear and switching to a delta-based
software tracking approach:
- 64-bit H+L registers (tx/rx ok pkts, ok bytes, E64..L1023):
read the absolute hardware total directly each poll.
- 32-bit registers (drops, bc, mc, errors, runt, long, ...):
store the previous raw register value in mib_prev and accumulate
(u32)(curr - prev) into a 64-bit software counter. Unsigned
subtraction handles wrap-around transparently.
- tx_len[0]/rx_len[0] ({0,64} RMON bucket) combines RUNT_CNT
(32-bit, delta-tracked via mib_prev.tx_runt_cnt) and E64_CNT
(64-bit, absolute). A u64 accumulator tx_runt_accum64 holds the
running RUNT delta sum so that each poll sets:
tx_len[0] = tx_runt_accum64 + E64_abs
without double-counting the E64 value.
Merge airoha_dev_get_hw_stats() into airoha_update_hw_stats(),
moving the port spin_lock inside so callers do not need a separate
wrapper.
Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
---
Changes in v2:
- Store _CNT_L register reads in val before adding to stats, improving
readability (suggested by Lorenzo Bianconi)
- Fix double-counting bug in the RUNT+E64 combined bucket: previously
"+=" for E64 re-added the full absolute counter each poll; now a
dedicated tx_runt_accum64/rx_runt_accum64 accumulator holds the
running RUNT delta, and tx_len[0] is assigned (not accumulated) each
poll as runt_accum64 + E64_abs
- Replace 7-element tx_len[]/rx_len[] shadow arrays in mib_prev with
focused tx_runt_cnt/tx_long_cnt and rx_runt_cnt/rx_long_cnt fields;
only RUNT and LONG are 32-bit and need wrap-around tracking
- Rename inner struct hw_prev_stats to mib_prev; rename accumulator
fields to tx_runt_accum64/rx_runt_accum64 for clarity
- Fix comment alignment in mib_prev struct block
- Rename airoha_dev_get_hw_stats() to airoha_update_hw_stats() and
move the port spin_lock inside, removing the separate wrapper
drivers/net/ethernet/airoha/airoha_eth.c | 115 +++++++++++++----------
drivers/net/ethernet/airoha/airoha_eth.h | 27 ++++++
2 files changed, 92 insertions(+), 50 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 59001fd4b6f7..4b7c547de165 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1686,12 +1686,14 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
}
}
-static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
+static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
{
struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
u32 val, i = 0;
+ spin_lock(&port->stats_lock);
+
/* Read relevant MIB for GDM with multiple port attached */
if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
airoha_fe_rmw(eth, REG_FE_GDM_MIB_CFG(port->id),
@@ -1701,152 +1703,165 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
u64_stats_update_begin(&dev->stats.syncp);
- /* TX */
+ /* TX - 64-bit H+L registers: hw accumulates the total, read directly. */
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
- dev->stats.tx_ok_pkts += ((u64)val << 32);
+ dev->stats.tx_ok_pkts = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
dev->stats.tx_ok_pkts += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
- dev->stats.tx_ok_bytes += ((u64)val << 32);
+ dev->stats.tx_ok_bytes = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
dev->stats.tx_ok_bytes += val;
+ /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
- dev->stats.tx_drops += val;
+ dev->stats.tx_drops += (u32)(val - dev->stats.mib_prev.tx_drops);
+ dev->stats.mib_prev.tx_drops = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
- dev->stats.tx_broadcast += val;
+ dev->stats.tx_broadcast += (u32)(val - dev->stats.mib_prev.tx_broadcast);
+ dev->stats.mib_prev.tx_broadcast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
- dev->stats.tx_multicast += val;
+ dev->stats.tx_multicast += (u32)(val - dev->stats.mib_prev.tx_multicast);
+ dev->stats.mib_prev.tx_multicast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
- dev->stats.tx_len[i] += val;
+ dev->stats.mib_prev.tx_runt_accum64 +=
+ (u32)(val - dev->stats.mib_prev.tx_runt_cnt);
+ dev->stats.mib_prev.tx_runt_cnt = val;
+
+ /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
+ * Accumulate RUNT delta in tx_runt_accum64, then assign tx_len[0] as
+ * accum + E64_abs so each call gives the correct combined total.
+ */
+
+ dev->stats.tx_len[i] = dev->stats.mib_prev.tx_runt_accum64;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
- dev->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
- dev->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += (u32)(val - dev->stats.mib_prev.tx_long_cnt);
+ dev->stats.mib_prev.tx_long_cnt = val;
/* RX */
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
- dev->stats.rx_ok_pkts += ((u64)val << 32);
+ dev->stats.rx_ok_pkts = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
dev->stats.rx_ok_pkts += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
- dev->stats.rx_ok_bytes += ((u64)val << 32);
+ dev->stats.rx_ok_bytes = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
dev->stats.rx_ok_bytes += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
- dev->stats.rx_drops += val;
+ dev->stats.rx_drops += (u32)(val - dev->stats.mib_prev.rx_drops);
+ dev->stats.mib_prev.rx_drops = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
- dev->stats.rx_broadcast += val;
+ dev->stats.rx_broadcast += (u32)(val - dev->stats.mib_prev.rx_broadcast);
+ dev->stats.mib_prev.rx_broadcast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
- dev->stats.rx_multicast += val;
+ dev->stats.rx_multicast += (u32)(val - dev->stats.mib_prev.rx_multicast);
+ dev->stats.mib_prev.rx_multicast = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
- dev->stats.rx_errors += val;
+ dev->stats.rx_errors += (u32)(val - dev->stats.mib_prev.rx_errors);
+ dev->stats.mib_prev.rx_errors = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
- dev->stats.rx_crc_error += val;
+ dev->stats.rx_crc_error += (u32)(val - dev->stats.mib_prev.rx_crc_error);
+ dev->stats.mib_prev.rx_crc_error = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
- dev->stats.rx_over_errors += val;
+ dev->stats.rx_over_errors += (u32)(val - dev->stats.mib_prev.rx_over_errors);
+ dev->stats.mib_prev.rx_over_errors = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
- dev->stats.rx_fragment += val;
+ dev->stats.rx_fragment += (u32)(val - dev->stats.mib_prev.rx_fragment);
+ dev->stats.mib_prev.rx_fragment = val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
- dev->stats.rx_jabber += val;
+ dev->stats.rx_jabber += (u32)(val - dev->stats.mib_prev.rx_jabber);
+ dev->stats.mib_prev.rx_jabber = val;
i = 0;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
- dev->stats.rx_len[i] += val;
+ dev->stats.mib_prev.rx_runt_accum64 +=
+ (u32)(val - dev->stats.mib_prev.rx_runt_cnt);
+ dev->stats.mib_prev.rx_runt_cnt = val;
+
+ /* rx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
+ * then assign rx_len[0] = rx_runt_accum64 + E64_abs.
+ */
+ dev->stats.rx_len[i] = dev->stats.mib_prev.rx_runt_accum64;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
- dev->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] = (u64)val << 32;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
- dev->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i] += (u32)(val - dev->stats.mib_prev.rx_long_cnt);
+ dev->stats.mib_prev.rx_long_cnt = val;
u64_stats_update_end(&dev->stats.syncp);
-}
-
-static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
-{
- struct airoha_gdm_port *port = dev->port;
- int i;
-
- spin_lock(&port->stats_lock);
-
- for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
- if (port->devs[i])
- airoha_dev_get_hw_stats(port->devs[i]);
- }
-
- /* Reset MIB counters */
- airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
- FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
spin_unlock(&port->stats_lock);
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index f6d01a8e8da1..3af1c49dd62d 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -245,6 +245,33 @@ struct airoha_hw_stats {
u64 rx_fragment;
u64 rx_jabber;
u64 rx_len[7];
+
+ struct {
+ /* Previous HW register values for 32-bit counter delta
+ * tracking. Storing the last seen value and accumulating
+ * (u32)(curr - prev) into the 64-bit software counter
+ * handles wrap-around transparently via unsigned arithmetic.
+ * tx_runt_accum64/rx_runt_accum64 hold the running sum of
+ * runt deltas. These fields are never reported to userspace.
+ */
+ u32 tx_drops;
+ u32 tx_broadcast;
+ u32 tx_multicast;
+ u32 tx_runt_cnt;
+ u32 tx_long_cnt;
+ u64 tx_runt_accum64;
+ u32 rx_drops;
+ u32 rx_broadcast;
+ u32 rx_multicast;
+ u32 rx_errors;
+ u32 rx_crc_error;
+ u32 rx_over_errors;
+ u32 rx_fragment;
+ u32 rx_jabber;
+ u32 rx_runt_cnt;
+ u32 rx_long_cnt;
+ u64 rx_runt_accum64;
+ } mib_prev;
};
enum {
base-commit: a225f8c20712713406ae47024b8df42deacddd4a
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net v2] net: airoha: fix MIB stats collection to be lossless
2026-07-01 17:39 ` [PATCH net v2] " Aniket Negi
@ 2026-07-01 22:43 ` Lorenzo Bianconi
0 siblings, 0 replies; 7+ messages in thread
From: Lorenzo Bianconi @ 2026-07-01 22:43 UTC (permalink / raw)
To: Aniket Negi
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 14296 bytes --]
> The current driver resets hardware MIB counters after every read via
> REG_FE_GDM_MIB_CLEAR. This creates a race window: packets arriving
> between the read and the clear are silently lost from statistics.
>
> Fix this by removing the MIB clear and switching to a delta-based
> software tracking approach:
>
> - 64-bit H+L registers (tx/rx ok pkts, ok bytes, E64..L1023):
> read the absolute hardware total directly each poll.
>
> - 32-bit registers (drops, bc, mc, errors, runt, long, ...):
> store the previous raw register value in mib_prev and accumulate
> (u32)(curr - prev) into a 64-bit software counter. Unsigned
> subtraction handles wrap-around transparently.
>
> - tx_len[0]/rx_len[0] ({0,64} RMON bucket) combines RUNT_CNT
> (32-bit, delta-tracked via mib_prev.tx_runt_cnt) and E64_CNT
> (64-bit, absolute). A u64 accumulator tx_runt_accum64 holds the
> running RUNT delta sum so that each poll sets:
> tx_len[0] = tx_runt_accum64 + E64_abs
> without double-counting the E64 value.
>
> Merge airoha_dev_get_hw_stats() into airoha_update_hw_stats(),
> moving the port spin_lock inside so callers do not need a separate
> wrapper.
>
> Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
Hi Aniket,
just few nits inline. Fixing them:
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
>
> Changes in v2:
> - Store _CNT_L register reads in val before adding to stats, improving
> readability (suggested by Lorenzo Bianconi)
> - Fix double-counting bug in the RUNT+E64 combined bucket: previously
> "+=" for E64 re-added the full absolute counter each poll; now a
> dedicated tx_runt_accum64/rx_runt_accum64 accumulator holds the
> running RUNT delta, and tx_len[0] is assigned (not accumulated) each
> poll as runt_accum64 + E64_abs
> - Replace 7-element tx_len[]/rx_len[] shadow arrays in mib_prev with
> focused tx_runt_cnt/tx_long_cnt and rx_runt_cnt/rx_long_cnt fields;
> only RUNT and LONG are 32-bit and need wrap-around tracking
> - Rename inner struct hw_prev_stats to mib_prev; rename accumulator
> fields to tx_runt_accum64/rx_runt_accum64 for clarity
> - Fix comment alignment in mib_prev struct block
> - Rename airoha_dev_get_hw_stats() to airoha_update_hw_stats() and
> move the port spin_lock inside, removing the separate wrapper
>
> drivers/net/ethernet/airoha/airoha_eth.c | 115 +++++++++++++----------
> drivers/net/ethernet/airoha/airoha_eth.h | 27 ++++++
> 2 files changed, 92 insertions(+), 50 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 59001fd4b6f7..4b7c547de165 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -1686,12 +1686,14 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
> }
> }
>
> -static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
> +static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> {
> struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = dev->eth;
> u32 val, i = 0;
>
> + spin_lock(&port->stats_lock);
> +
> /* Read relevant MIB for GDM with multiple port attached */
> if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
> airoha_fe_rmw(eth, REG_FE_GDM_MIB_CFG(port->id),
> @@ -1701,152 +1703,165 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
>
> u64_stats_update_begin(&dev->stats.syncp);
>
> - /* TX */
> + /* TX - 64-bit H+L registers: hw accumulates the total, read directly. */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
> - dev->stats.tx_ok_pkts += ((u64)val << 32);
> + dev->stats.tx_ok_pkts = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> dev->stats.tx_ok_pkts += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
> - dev->stats.tx_ok_bytes += ((u64)val << 32);
> + dev->stats.tx_ok_bytes = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
> dev->stats.tx_ok_bytes += val;
>
> + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
> - dev->stats.tx_drops += val;
> + dev->stats.tx_drops += (u32)(val - dev->stats.mib_prev.tx_drops);
> + dev->stats.mib_prev.tx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
> - dev->stats.tx_broadcast += val;
> + dev->stats.tx_broadcast += (u32)(val - dev->stats.mib_prev.tx_broadcast);
> + dev->stats.mib_prev.tx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
> - dev->stats.tx_multicast += val;
> + dev->stats.tx_multicast += (u32)(val - dev->stats.mib_prev.tx_multicast);
> + dev->stats.mib_prev.tx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
> - dev->stats.tx_len[i] += val;
> + dev->stats.mib_prev.tx_runt_accum64 +=
I guess dev->stats.mib_prev.tx_runt64
> + (u32)(val - dev->stats.mib_prev.tx_runt_cnt);
dev->stats.mib_prev.tx_runt64 += (u32)(val -
dev->stats.mib_prev.tx_runt);
> + dev->stats.mib_prev.tx_runt_cnt = val;
> +
> + /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
> + * Accumulate RUNT delta in tx_runt_accum64, then assign tx_len[0] as
> + * accum + E64_abs so each call gives the correct combined total.
> + */
no new-line here.
> +
> + dev->stats.tx_len[i] = dev->stats.mib_prev.tx_runt_accum64;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] += (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + dev->stats.tx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
> dev->stats.tx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i++] += (u32)(val - dev->stats.mib_prev.tx_long_cnt);
> + dev->stats.mib_prev.tx_long_cnt = val;
>
> /* RX */
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
> - dev->stats.rx_ok_pkts += ((u64)val << 32);
> + dev->stats.rx_ok_pkts = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
> dev->stats.rx_ok_pkts += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
> - dev->stats.rx_ok_bytes += ((u64)val << 32);
> + dev->stats.rx_ok_bytes = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
> dev->stats.rx_ok_bytes += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
> - dev->stats.rx_drops += val;
> + dev->stats.rx_drops += (u32)(val - dev->stats.mib_prev.rx_drops);
> + dev->stats.mib_prev.rx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
> - dev->stats.rx_broadcast += val;
> + dev->stats.rx_broadcast += (u32)(val - dev->stats.mib_prev.rx_broadcast);
> + dev->stats.mib_prev.rx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
> - dev->stats.rx_multicast += val;
> + dev->stats.rx_multicast += (u32)(val - dev->stats.mib_prev.rx_multicast);
> + dev->stats.mib_prev.rx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
> - dev->stats.rx_errors += val;
> + dev->stats.rx_errors += (u32)(val - dev->stats.mib_prev.rx_errors);
> + dev->stats.mib_prev.rx_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
> - dev->stats.rx_crc_error += val;
> + dev->stats.rx_crc_error += (u32)(val - dev->stats.mib_prev.rx_crc_error);
> + dev->stats.mib_prev.rx_crc_error = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
> - dev->stats.rx_over_errors += val;
> + dev->stats.rx_over_errors += (u32)(val - dev->stats.mib_prev.rx_over_errors);
> + dev->stats.mib_prev.rx_over_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
> - dev->stats.rx_fragment += val;
> + dev->stats.rx_fragment += (u32)(val - dev->stats.mib_prev.rx_fragment);
> + dev->stats.mib_prev.rx_fragment = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
> - dev->stats.rx_jabber += val;
> + dev->stats.rx_jabber += (u32)(val - dev->stats.mib_prev.rx_jabber);
> + dev->stats.mib_prev.rx_jabber = val;
>
> i = 0;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
> - dev->stats.rx_len[i] += val;
> + dev->stats.mib_prev.rx_runt_accum64 +=
> + (u32)(val - dev->stats.mib_prev.rx_runt_cnt);
ditto.
> + dev->stats.mib_prev.rx_runt_cnt = val;
> +
> + /* rx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
> + * then assign rx_len[0] = rx_runt_accum64 + E64_abs.
> + */
>
ditto.
> + dev->stats.rx_len[i] = dev->stats.mib_prev.rx_runt_accum64;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] += (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + dev->stats.rx_len[i] = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
> dev->stats.rx_len[i++] += val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] += (u32)(val - dev->stats.mib_prev.rx_long_cnt);
> + dev->stats.mib_prev.rx_long_cnt = val;
>
> u64_stats_update_end(&dev->stats.syncp);
> -}
> -
> -static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> -{
> - struct airoha_gdm_port *port = dev->port;
> - int i;
> -
> - spin_lock(&port->stats_lock);
> -
> - for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
> - if (port->devs[i])
> - airoha_dev_get_hw_stats(port->devs[i]);
> - }
> -
> - /* Reset MIB counters */
> - airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
> - FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
>
> spin_unlock(&port->stats_lock);
> }
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index f6d01a8e8da1..3af1c49dd62d 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -245,6 +245,33 @@ struct airoha_hw_stats {
> u64 rx_fragment;
> u64 rx_jabber;
> u64 rx_len[7];
> +
> + struct {
> + /* Previous HW register values for 32-bit counter delta
> + * tracking. Storing the last seen value and accumulating
> + * (u32)(curr - prev) into the 64-bit software counter
> + * handles wrap-around transparently via unsigned arithmetic.
> + * tx_runt_accum64/rx_runt_accum64 hold the running sum of
> + * runt deltas. These fields are never reported to userspace.
> + */
> + u32 tx_drops;
> + u32 tx_broadcast;
> + u32 tx_multicast;
> + u32 tx_runt_cnt;
u32 tx_runt;
> + u32 tx_long_cnt;
u32 tx_long;
> + u64 tx_runt_accum64;
64 tx_runt64;
> + u32 rx_drops;
> + u32 rx_broadcast;
> + u32 rx_multicast;
> + u32 rx_errors;
> + u32 rx_crc_error;
> + u32 rx_over_errors;
> + u32 rx_fragment;
> + u32 rx_jabber;
> + u32 rx_runt_cnt;
u32 rx_runt;
> + u32 rx_long_cnt;
u32 rx_long;
> + u64 rx_runt_accum64;
u64 rx_runt64;
> + } mib_prev;
> };
>
> enum {
>
> base-commit: a225f8c20712713406ae47024b8df42deacddd4a
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-01 22:43 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 11:18 [PATCH] net: airoha: fix MIB stats collection to be lossless Aniket Negi
2026-06-30 14:21 ` Lorenzo Bianconi
2026-07-01 6:38 ` Aniket Negi
2026-07-01 6:51 ` Lorenzo Bianconi
2026-07-01 10:29 ` Aniket Negi
2026-07-01 17:39 ` [PATCH net v2] " Aniket Negi
2026-07-01 22:43 ` Lorenzo Bianconi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox