* [PATCH net-next] net: dsa: qca8k: expose cached per-port MIB counters via get_stats64
@ 2026-10-30 21:58 Salah Ahmed
2026-10-30 22:17 ` Jakub Kicinski
0 siblings, 1 reply; 3+ messages in thread
From: Salah Ahmed @ 2026-10-30 21:58 UTC (permalink / raw)
To: Christian Marangi, Andrew Lunn, Vladimir Oltean, netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King, linux-kernel
DSA user port netdev statistics count only CPU-seen traffic, so bridge
offloaded forwarding is invisible to /sys/class/net/<port>/statistics, to
rtnetlink IFLA_STATS and to every kernel consumer of them, including the
netdev LED trigger. The switch MIB counters see every frame crossing the
port, but reading them costs a register transaction per counter on a bus
that is either MDIO or MDIO-over-Ethernet, while consumers such as the LED
trigger call dev_get_stats() several times per second.
Use the pattern that is already established for switch drivers: ar9331 in
this same directory caches a rtnl_link_stats64 behind a spinlock, refreshes
it from a delayed work and serves it from .get_stats64, and ksz does the
same. Poll RxGoodByte/TxByte per user port and serve the cached values.
The poll is adaptive rather than fixed. A consumer that derives a rate
from these counters only sees the wire when the cache moves, so a fixed
period sets the latency of every rate-based consumer to that period.
Refresh four times a second while the counters move and once a second
while they are idle: an idle switch stays cheap, and a busy one is
reported within a quarter of a second. The work also runs once as soon as
it is armed, so the first consumer after a link comes up does not read
zeros for a whole period.
The work is scheduled with schedule_delayed_work(), i.e. on system_wq like
ar9331 does, and not on system_power_efficient_wq. With
CONFIG_WQ_POWER_EFFICIENT_DEFAULT the power-efficient workqueue arms
deferrable timers, which do not wake an idle CPU; on a single-core switch
SoC that is mostly idle this deferred a nominal 3 s poll by several more
seconds, and consumers saw the wire that late.
DSA serves a user port either from the driver's .get_stats64 or from
dev_get_tstats64(), never from both, so the cache has to be layered on top
of the software counters: start from dev_get_tstats64() and override only
the byte counters with the wire values. Otherwise installing .get_stats64
would drop rx_packets/tx_packets to zero and break every consumer that
looks at packets, including the netdev LED trigger in tx/rx mode. The MIB
counts every frame crossing the port including the CPU-bound ones, so the
wire byte counters are a superset of the software ones and replacing them
loses nothing.
Each 64-bit counter is read with a single regmap_bulk_read() so that no
other register access can land between the low and the high half of the
same counter.
The cache fields sit at the top of struct qca8k_priv to stay clear of
target-specific patches that extend its tail, such as the ipq40xx IPQ4019
built-in switch support. The poll work is armed at the end of
qca8k_setup(): a failing setup never reaches the remove callback, so
arming it earlier would leave the delayed work running after the driver is
gone.
The poll work is armed at the end of qca8k_setup() and stopped from a new
.teardown op. DSA calls .teardown both on the dsa_switch_setup() error
paths that follow a successful ops->setup() - the tag protocol setup and
the user MDIO bus allocation and registration - and from
dsa_unregister_switch(), so the work cannot outlive the driver data it
reads. Stopping it in qca8k_sw_remove() alone misses the probe error
path: dsa_register_switch() fails, probe fails, devm frees priv and the
armed work then runs on freed memory.
Signed-off-by: Salah Ahmed <haddad2inc@gmail.com>
---
drivers/net/dsa/qca/qca8k-8xxx.c | 17 +++++
drivers/net/dsa/qca/qca8k-common.c | 110 +++++++++++++++++++++++++++++
drivers/net/dsa/qca/qca8k.h | 15 ++++
3 files changed, 142 insertions(+)
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 07227ae8bc..08f3ef00e6 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -2020,6 +2020,14 @@ qca8k_setup(struct dsa_switch *ds)
/* Set max number of LAGs supported */
ds->num_lag_ids = QCA8K_NUM_LAGS;
+ /* Arm the MIB poll work only after every failure path in this
+ * function is behind us. DSA calls .teardown both on the
+ * dsa_switch_setup() error paths that follow a successful
+ * ops->setup() and from dsa_unregister_switch(), so the work is
+ * always stopped again.
+ */
+ qca8k_mib_poll_start(priv);
+
return 0;
}
@@ -2030,11 +2038,20 @@ static const struct phylink_mac_ops qca8k_phylink_mac_ops = {
.mac_link_up = qca8k_phylink_mac_link_up,
};
+static void qca8k_teardown(struct dsa_switch *ds)
+{
+ struct qca8k_priv *priv = ds->priv;
+
+ qca8k_mib_poll_stop(priv);
+}
+
static const struct dsa_switch_ops qca8k_switch_ops = {
.get_tag_protocol = qca8k_get_tag_protocol,
.setup = qca8k_setup,
+ .teardown = qca8k_teardown,
.get_strings = qca8k_get_strings,
.get_ethtool_stats = qca8k_get_ethtool_stats,
+ .get_stats64 = qca8k_get_stats64,
.get_sset_count = qca8k_get_sset_count,
.set_ageing_time = qca8k_set_ageing_time,
.support_eee = dsa_supports_eee,
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index ab978cc836..7c446f507f 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -520,6 +520,116 @@ void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
}
}
+/* Two cadences, picked per round by the work itself: while the wire counters
+ * move the cache is refreshed four times a second so that a rate consumer
+ * sees the traffic almost immediately, and once a second while they are
+ * idle, which keeps the MDIO cost of an idle switch low.
+ *
+ * schedule_delayed_work() (system_wq) is used deliberately instead of a
+ * power-efficient workqueue: with CONFIG_WQ_POWER_EFFICIENT_DEFAULT the
+ * latter arms deferrable timers that do not wake an idle CPU, which on a
+ * single-core switch SoC delayed a 3 s poll by several more seconds and
+ * made consumers see the wire late. ar9331 schedules its equivalent MIB
+ * work the same way.
+ */
+#define QCA8K_MIB_POLL_IDLE HZ
+#define QCA8K_MIB_POLL_ACTIVE (HZ / 4)
+
+/* RxGoodByte / TxByte offsets in the per-port MIB block, both 64-bit wide */
+#define QCA8K_MIB_RXGOODBYTE 0x3c
+#define QCA8K_MIB_TXBYTE 0x84
+
+/* Read one 64-bit MIB counter in a single regmap transaction, so that no
+ * other register access can land between the low and the high half.
+ */
+static int qca8k_mib_read_wire(struct qca8k_priv *priv, int port, u32 off,
+ u64 *val)
+{
+ u32 raw[2];
+ int ret;
+
+ ret = regmap_bulk_read(priv->regmap, QCA8K_PORT_MIB_COUNTER(port) + off,
+ raw, ARRAY_SIZE(raw));
+ if (ret)
+ return ret;
+
+ *val = raw[0] | ((u64)raw[1] << 32);
+
+ return 0;
+}
+
+static void qca8k_mib_poll_work_fn(struct work_struct *work)
+{
+ struct qca8k_priv *priv = container_of(work, struct qca8k_priv,
+ mib_poll_work.work);
+ struct dsa_switch *ds = priv->ds;
+ bool busy = false;
+ u64 rx, tx;
+ int port;
+
+ for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+ if (!dsa_is_user_port(ds, port))
+ continue;
+ if (qca8k_mib_read_wire(priv, port, QCA8K_MIB_RXGOODBYTE, &rx))
+ continue;
+ if (qca8k_mib_read_wire(priv, port, QCA8K_MIB_TXBYTE, &tx))
+ continue;
+
+ spin_lock_bh(&priv->stats_lock);
+ if (priv->port_stats[port].rx_bytes != rx ||
+ priv->port_stats[port].tx_bytes != tx)
+ busy = true;
+ priv->port_stats[port].rx_bytes = rx;
+ priv->port_stats[port].tx_bytes = tx;
+ spin_unlock_bh(&priv->stats_lock);
+ }
+
+ schedule_delayed_work(&priv->mib_poll_work,
+ busy ? QCA8K_MIB_POLL_ACTIVE : QCA8K_MIB_POLL_IDLE);
+}
+
+/* Serve the wire counters on top of the software ones. May be called in
+ * atomic context, so only the cache is touched here. The MIB sees every
+ * frame crossing the port, including bridge-offloaded traffic that never
+ * reaches the CPU, so DSA user ports finally report the truth on the wire
+ * to every standard kernel consumer, without ethtool or userspace helpers.
+ *
+ * DSA calls either .get_stats64 or dev_get_tstats64(), never both, so the
+ * packet and error counters collected by the DSA xmit/receive paths have to
+ * be picked up here as well or they would be lost.
+ */
+void qca8k_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s)
+{
+ struct qca8k_priv *priv = ds->priv;
+ struct rtnl_link_stats64 wire;
+
+ dev_get_tstats64(dsa_to_port(ds, port)->user, s);
+
+ spin_lock_bh(&priv->stats_lock);
+ wire = priv->port_stats[port];
+ spin_unlock_bh(&priv->stats_lock);
+
+ s->rx_bytes = wire.rx_bytes;
+ s->tx_bytes = wire.tx_bytes;
+}
+
+void qca8k_mib_poll_start(struct qca8k_priv *priv)
+{
+ spin_lock_init(&priv->stats_lock);
+ INIT_DELAYED_WORK(&priv->mib_poll_work, qca8k_mib_poll_work_fn);
+
+ /* Read once right away: without it the first consumer after a link
+ * comes up sees zeros for a whole poll period.
+ */
+ schedule_delayed_work(&priv->mib_poll_work, 0);
+}
+
+void qca8k_mib_poll_stop(struct qca8k_priv *priv)
+{
+ cancel_delayed_work_sync(&priv->mib_poll_work);
+}
+
int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct qca8k_priv *priv = ds->priv;
diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h
index 9563388930..edab7e1218 100644
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/drivers/net/dsa/qca/qca8k.h
@@ -443,6 +443,15 @@ struct qca8k_led {
};
struct qca8k_priv {
+ /* Cached per-port wire statistics from the switch MIB, refreshed by
+ * mib_poll_work so that .get_stats64 (atomic context) never sleeps.
+ * Placed at the top of the struct to stay clear of target-specific
+ * patches that extend its tail (e.g. ipq40xx IPQ4019 support).
+ */
+ struct rtnl_link_stats64 port_stats[QCA8K_NUM_PORTS];
+ spinlock_t stats_lock; /* guards port_stats[] */
+ struct delayed_work mib_poll_work;
+
u8 switch_id;
u8 switch_revision;
u8 mirror_rx;
@@ -513,6 +522,12 @@ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
/* Common ops function */
void qca8k_fdb_flush(struct qca8k_priv *priv);
+/* Cached MIB statistics served from atomic context */
+void qca8k_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s);
+void qca8k_mib_poll_start(struct qca8k_priv *priv);
+void qca8k_mib_poll_stop(struct qca8k_priv *priv);
+
/* Common ethtool stats function */
void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data);
void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH net-next] net: dsa: qca8k: expose cached per-port MIB counters via get_stats64
2026-10-30 21:58 [PATCH net-next] net: dsa: qca8k: expose cached per-port MIB counters via get_stats64 Salah Ahmed
@ 2026-10-30 22:17 ` Jakub Kicinski
0 siblings, 0 replies; 3+ messages in thread
From: Jakub Kicinski @ 2026-10-30 22:17 UTC (permalink / raw)
To: Salah Ahmed
Cc: Christian Marangi, Andrew Lunn, Vladimir Oltean, netdev,
David S. Miller, Eric Dumazet, Paolo Abeni, Russell King,
linux-kernel
On Sat, 31 Oct 2026 00:58:31 +0300 Salah Ahmed wrote:
> DSA user port netdev statistics count only CPU-seen traffic, so bridge
> offloaded forwarding is invisible to /sys/class/net/<port>/statistics, to
> rtnetlink IFLA_STATS and to every kernel consumer of them
net-next is closed, please repost in a week.
And when you do please trim the slop form the commit msg.
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH net-next] net: dsa: qca8k: expose cached per-port MIB counters via get_stats64
@ 2026-10-30 21:55 Salah Ahmed
0 siblings, 0 replies; 3+ messages in thread
From: Salah Ahmed @ 2026-10-30 21:55 UTC (permalink / raw)
To: Christian Marangi, Andrew Lunn, Vladimir Oltean, netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King, linux-kernel
DSA user port netdev statistics count only CPU-seen traffic, so bridge
offloaded forwarding is invisible to /sys/class/net/<port>/statistics, to
rtnetlink IFLA_STATS and to every kernel consumer of them, including the
netdev LED trigger. The switch MIB counters see every frame crossing the
port, but reading them costs a register transaction per counter on a bus
that is either MDIO or MDIO-over-Ethernet, while consumers such as the LED
trigger call dev_get_stats() several times per second.
Use the pattern that is already established for switch drivers: ar9331 in
this same directory caches a rtnl_link_stats64 behind a spinlock, refreshes
it from a delayed work and serves it from .get_stats64, and ksz does the
same. Poll RxGoodByte/TxByte per user port and serve the cached values.
The poll is adaptive rather than fixed. A consumer that derives a rate
from these counters only sees the wire when the cache moves, so a fixed
period sets the latency of every rate-based consumer to that period.
Refresh four times a second while the counters move and once a second
while they are idle: an idle switch stays cheap, and a busy one is
reported within a quarter of a second. The work also runs once as soon as
it is armed, so the first consumer after a link comes up does not read
zeros for a whole period.
The work is scheduled with schedule_delayed_work(), i.e. on system_wq like
ar9331 does, and not on system_power_efficient_wq. With
CONFIG_WQ_POWER_EFFICIENT_DEFAULT the power-efficient workqueue arms
deferrable timers, which do not wake an idle CPU; on a single-core switch
SoC that is mostly idle this deferred a nominal 3 s poll by several more
seconds, and consumers saw the wire that late.
DSA serves a user port either from the driver's .get_stats64 or from
dev_get_tstats64(), never from both, so the cache has to be layered on top
of the software counters: start from dev_get_tstats64() and override only
the byte counters with the wire values. Otherwise installing .get_stats64
would drop rx_packets/tx_packets to zero and break every consumer that
looks at packets, including the netdev LED trigger in tx/rx mode. The MIB
counts every frame crossing the port including the CPU-bound ones, so the
wire byte counters are a superset of the software ones and replacing them
loses nothing.
Each 64-bit counter is read with a single regmap_bulk_read() so that no
other register access can land between the low and the high half of the
same counter.
The cache fields sit at the top of struct qca8k_priv to stay clear of
target-specific patches that extend its tail, such as the ipq40xx IPQ4019
built-in switch support. The poll work is armed at the end of
qca8k_setup(): a failing setup never reaches the remove callback, so
arming it earlier would leave the delayed work running after the driver is
gone.
The poll work is armed at the end of qca8k_setup() and stopped from a new
.teardown op. DSA calls .teardown both on the dsa_switch_setup() error
paths that follow a successful ops->setup() - the tag protocol setup and
the user MDIO bus allocation and registration - and from
dsa_unregister_switch(), so the work cannot outlive the driver data it
reads. Stopping it in qca8k_sw_remove() alone misses the probe error
path: dsa_register_switch() fails, probe fails, devm frees priv and the
armed work then runs on freed memory.
Signed-off-by: Salah Ahmed <haddad2inc@gmail.com>
---
drivers/net/dsa/qca/qca8k-8xxx.c | 17 +++++
drivers/net/dsa/qca/qca8k-common.c | 110 +++++++++++++++++++++++++++++
drivers/net/dsa/qca/qca8k.h | 15 ++++
3 files changed, 142 insertions(+)
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 07227ae8bc..08f3ef00e6 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -2020,6 +2020,14 @@ qca8k_setup(struct dsa_switch *ds)
/* Set max number of LAGs supported */
ds->num_lag_ids = QCA8K_NUM_LAGS;
+ /* Arm the MIB poll work only after every failure path in this
+ * function is behind us. DSA calls .teardown both on the
+ * dsa_switch_setup() error paths that follow a successful
+ * ops->setup() and from dsa_unregister_switch(), so the work is
+ * always stopped again.
+ */
+ qca8k_mib_poll_start(priv);
+
return 0;
}
@@ -2030,11 +2038,20 @@ static const struct phylink_mac_ops qca8k_phylink_mac_ops = {
.mac_link_up = qca8k_phylink_mac_link_up,
};
+static void qca8k_teardown(struct dsa_switch *ds)
+{
+ struct qca8k_priv *priv = ds->priv;
+
+ qca8k_mib_poll_stop(priv);
+}
+
static const struct dsa_switch_ops qca8k_switch_ops = {
.get_tag_protocol = qca8k_get_tag_protocol,
.setup = qca8k_setup,
+ .teardown = qca8k_teardown,
.get_strings = qca8k_get_strings,
.get_ethtool_stats = qca8k_get_ethtool_stats,
+ .get_stats64 = qca8k_get_stats64,
.get_sset_count = qca8k_get_sset_count,
.set_ageing_time = qca8k_set_ageing_time,
.support_eee = dsa_supports_eee,
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index ab978cc836..7c446f507f 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -520,6 +520,116 @@ void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
}
}
+/* Two cadences, picked per round by the work itself: while the wire counters
+ * move the cache is refreshed four times a second so that a rate consumer
+ * sees the traffic almost immediately, and once a second while they are
+ * idle, which keeps the MDIO cost of an idle switch low.
+ *
+ * schedule_delayed_work() (system_wq) is used deliberately instead of a
+ * power-efficient workqueue: with CONFIG_WQ_POWER_EFFICIENT_DEFAULT the
+ * latter arms deferrable timers that do not wake an idle CPU, which on a
+ * single-core switch SoC delayed a 3 s poll by several more seconds and
+ * made consumers see the wire late. ar9331 schedules its equivalent MIB
+ * work the same way.
+ */
+#define QCA8K_MIB_POLL_IDLE HZ
+#define QCA8K_MIB_POLL_ACTIVE (HZ / 4)
+
+/* RxGoodByte / TxByte offsets in the per-port MIB block, both 64-bit wide */
+#define QCA8K_MIB_RXGOODBYTE 0x3c
+#define QCA8K_MIB_TXBYTE 0x84
+
+/* Read one 64-bit MIB counter in a single regmap transaction, so that no
+ * other register access can land between the low and the high half.
+ */
+static int qca8k_mib_read_wire(struct qca8k_priv *priv, int port, u32 off,
+ u64 *val)
+{
+ u32 raw[2];
+ int ret;
+
+ ret = regmap_bulk_read(priv->regmap, QCA8K_PORT_MIB_COUNTER(port) + off,
+ raw, ARRAY_SIZE(raw));
+ if (ret)
+ return ret;
+
+ *val = raw[0] | ((u64)raw[1] << 32);
+
+ return 0;
+}
+
+static void qca8k_mib_poll_work_fn(struct work_struct *work)
+{
+ struct qca8k_priv *priv = container_of(work, struct qca8k_priv,
+ mib_poll_work.work);
+ struct dsa_switch *ds = priv->ds;
+ bool busy = false;
+ u64 rx, tx;
+ int port;
+
+ for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+ if (!dsa_is_user_port(ds, port))
+ continue;
+ if (qca8k_mib_read_wire(priv, port, QCA8K_MIB_RXGOODBYTE, &rx))
+ continue;
+ if (qca8k_mib_read_wire(priv, port, QCA8K_MIB_TXBYTE, &tx))
+ continue;
+
+ spin_lock_bh(&priv->stats_lock);
+ if (priv->port_stats[port].rx_bytes != rx ||
+ priv->port_stats[port].tx_bytes != tx)
+ busy = true;
+ priv->port_stats[port].rx_bytes = rx;
+ priv->port_stats[port].tx_bytes = tx;
+ spin_unlock_bh(&priv->stats_lock);
+ }
+
+ schedule_delayed_work(&priv->mib_poll_work,
+ busy ? QCA8K_MIB_POLL_ACTIVE : QCA8K_MIB_POLL_IDLE);
+}
+
+/* Serve the wire counters on top of the software ones. May be called in
+ * atomic context, so only the cache is touched here. The MIB sees every
+ * frame crossing the port, including bridge-offloaded traffic that never
+ * reaches the CPU, so DSA user ports finally report the truth on the wire
+ * to every standard kernel consumer, without ethtool or userspace helpers.
+ *
+ * DSA calls either .get_stats64 or dev_get_tstats64(), never both, so the
+ * packet and error counters collected by the DSA xmit/receive paths have to
+ * be picked up here as well or they would be lost.
+ */
+void qca8k_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s)
+{
+ struct qca8k_priv *priv = ds->priv;
+ struct rtnl_link_stats64 wire;
+
+ dev_get_tstats64(dsa_to_port(ds, port)->user, s);
+
+ spin_lock_bh(&priv->stats_lock);
+ wire = priv->port_stats[port];
+ spin_unlock_bh(&priv->stats_lock);
+
+ s->rx_bytes = wire.rx_bytes;
+ s->tx_bytes = wire.tx_bytes;
+}
+
+void qca8k_mib_poll_start(struct qca8k_priv *priv)
+{
+ spin_lock_init(&priv->stats_lock);
+ INIT_DELAYED_WORK(&priv->mib_poll_work, qca8k_mib_poll_work_fn);
+
+ /* Read once right away: without it the first consumer after a link
+ * comes up sees zeros for a whole poll period.
+ */
+ schedule_delayed_work(&priv->mib_poll_work, 0);
+}
+
+void qca8k_mib_poll_stop(struct qca8k_priv *priv)
+{
+ cancel_delayed_work_sync(&priv->mib_poll_work);
+}
+
int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct qca8k_priv *priv = ds->priv;
diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h
index 9563388930..edab7e1218 100644
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/drivers/net/dsa/qca/qca8k.h
@@ -443,6 +443,15 @@ struct qca8k_led {
};
struct qca8k_priv {
+ /* Cached per-port wire statistics from the switch MIB, refreshed by
+ * mib_poll_work so that .get_stats64 (atomic context) never sleeps.
+ * Placed at the top of the struct to stay clear of target-specific
+ * patches that extend its tail (e.g. ipq40xx IPQ4019 support).
+ */
+ struct rtnl_link_stats64 port_stats[QCA8K_NUM_PORTS];
+ spinlock_t stats_lock; /* guards port_stats[] */
+ struct delayed_work mib_poll_work;
+
u8 switch_id;
u8 switch_revision;
u8 mirror_rx;
@@ -513,6 +522,12 @@ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
/* Common ops function */
void qca8k_fdb_flush(struct qca8k_priv *priv);
+/* Cached MIB statistics served from atomic context */
+void qca8k_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s);
+void qca8k_mib_poll_start(struct qca8k_priv *priv);
+void qca8k_mib_poll_stop(struct qca8k_priv *priv);
+
/* Common ethtool stats function */
void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data);
void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-30 22:17 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 21:58 [PATCH net-next] net: dsa: qca8k: expose cached per-port MIB counters via get_stats64 Salah Ahmed
2026-10-30 22:17 ` Jakub Kicinski
-- strict thread matches above, loose matches on Subject: below --
2026-10-30 21:55 Salah Ahmed
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox