From fca34d849131f783b4b22e6a4c90d0ba5d4d5f55 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:37 +0000 Subject: [PATCH] net: ethernet: mtk_eth_soc: drain the GDM MIB counters on a timer The GDMA MIB counters are clear-on-read: mtk_stats_update_mac() adds each register into a software accumulator, so a count that is never read is a count that is lost. The hardware raises an "almost full" condition to ask for a drain before that happens, and the driver tests for it in mtk_handle_status_irq(): if (unlikely(status2 & (MTK_GDM1_AF | MTK_GDM2_AF))) mtk_stats_update(eth); MTK_GDM1_AF and MTK_GDM2_AF are BIT(28) and BIT(29). Those definitions are unconditional and describe a two-GDM frame engine; they date from the MT7621/MT7623 layout. MT7988 has three GDMs, and there is no MTK_GDM3_AF, so at best the third GDM has never had a trigger at all. Measured on MT7988 (BPI-R4, NETSYS v3) with sticky per-bit accumulators on both frontend status registers, across idle and saturated 1G and 10G traffic: bits 28 and 29 of MTK_FE_INT_STATUS are never observed set. Bits 23, 24 and 25 are permanently asserted. mtk_stats_update() is therefore never reached through mtk_handle_status_irq() on this SoC. What remains is mtk_get_stats64() and the ethtool stats op, both of which drain the counters but only when userspace asks. With nothing asking, the 32-bit packet, error and drop counters roll over silently. 2**32 packets is under five minutes of 10G minimum-size traffic and under an hour at 1G. The byte counters are a 64-bit pair and are not affected. The correct v3 bit assignment is not documented in any public source, and three permanently asserted bits are not evidence of an almost-full condition, so do not guess at a new mask. Add a delayed work that drains the counters once a second regardless, in the same shape as reset.monitor_work. That is three orders of magnitude inside the wrap interval and costs about forty register reads per second. The AF test is left in place; it is free, and it will start working if and when the correct bits are established. Signed-off-by: Mihai Ordean --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 27 +++++++++++++++++++++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 9 +++++++ 2 files changed, 36 insertions(+) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index d1bcc8afefa7..0f38346b5ce3 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -1342,6 +1342,30 @@ static void mtk_stats_update(struct mtk_eth *eth) } } +/* MTK_GDM1_AF and MTK_GDM2_AF describe a two-GDM frame engine and are the + * only trigger the driver has for draining the counters from the hot path. + * On NETSYS v3 that trigger does not fire, so drain them on a timer as well. + */ +static void mtk_stats_work(struct work_struct *work) +{ + struct delayed_work *del_work = to_delayed_work(work); + struct mtk_eth *eth = container_of(del_work, struct mtk_eth, + stats_work); + + if (test_bit(MTK_HW_INIT, ð->state) && + !test_bit(MTK_RESETTING, ð->state)) { + /* mtk_stats_update() is written for the NAPI path and takes + * stats_lock with spin_trylock(). Keep the lock out of two + * different contexts. + */ + local_bh_disable(); + mtk_stats_update(eth); + local_bh_enable(); + } + + schedule_delayed_work(ð->stats_work, MTK_STATS_DRAIN_TIMEOUT); +} + static void mtk_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *storage) { @@ -5078,6 +5102,7 @@ static int mtk_cleanup(struct mtk_eth *eth) mtk_free_dev(eth); cancel_work_sync(ð->pending_work); cancel_delayed_work_sync(ð->reset.monitor_work); + cancel_delayed_work_sync(ð->stats_work); return 0; } @@ -6134,6 +6159,7 @@ static int mtk_probe(struct platform_device *pdev) INIT_WORK(ð->rx_dim.work, mtk_dim_rx); atomic_set(ð->reset.force, 0); INIT_DELAYED_WORK(ð->reset.monitor_work, mtk_hw_reset_monitor_work); + INIT_DELAYED_WORK(ð->stats_work, mtk_stats_work); eth->tx_dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; INIT_WORK(ð->tx_dim.work, mtk_dim_tx); @@ -6465,6 +6491,7 @@ static int mtk_probe(struct platform_device *pdev) platform_set_drvdata(pdev, eth); schedule_delayed_work(ð->reset.monitor_work, MTK_DMA_MONITOR_TIMEOUT); + schedule_delayed_work(ð->stats_work, MTK_STATS_DRAIN_TIMEOUT); return 0; diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h index 13de4a984d70..7150a7efa42f 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h @@ -1478,6 +1478,12 @@ struct mtk_soc_data { #define MTK_DMA_MONITOR_TIMEOUT msecs_to_jiffies(1000) +/* The GDM MIB counters are clear-on-read and the packet counters are only + * 32 bits wide, so they have to be drained on a schedule rather than only + * when something asks for statistics. + */ +#define MTK_STATS_DRAIN_TIMEOUT msecs_to_jiffies(1000) + /* currently no SoC has more than 3 macs */ #define MTK_MAX_DEVS 3 @@ -1570,6 +1576,9 @@ struct mtk_eth { const struct mtk_soc_data *soc; + /* periodic drain of the clear-on-read GDM MIB counters */ + struct delayed_work stats_work; + spinlock_t dim_lock; u32 rx_events;