Thread (36 messages) 36 messages, 3 authors, 2026-08-19

Re: [PATCH net-next v11 9/9] net: dsa: lan9645x: add port statistics

From: Jens Emil Schulz Ostergaard <hidden>
Date: 2026-08-19 13:26:04
Also in: lkml, netdev

This is a reply to:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260805-dsa_lan9645x_switch_driver_base-v11-0-007ebc983a0a%40microchip.com?part=9
quoted hunk ↗ jump to hunk
--- Patch [9]: [PATCH net-next v11 9/9] net: dsa: lan9645x: add port statistics ---
commit 237edd60065db45a5dc8339c001c3205645d61b0
Author: Jens Emil Schulz Østergaard [off-list ref]

net: dsa: lan9645x: add port statistics

This adds a new lan9645x_stats unit which exposes the per-port hardware
counters through the DSA ethtool hooks and ndo_get_stats64.  Since the chip
counters are 32 bit, a 64-bit software shadow is kept and refreshed by a
delayed work every three seconds.  No Link: tags are present in the commit
header.

A few questions below.
quoted
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
quoted
index fc604edb309b9..6a14ba3661625 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
[ ... ]
quoted
@@ -800,6 +866,18 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
 	/* Multicast database */
 	.port_mdb_add			= lan9645x_mdb_add,
 	.port_mdb_del			= lan9645x_mdb_del,
+
+	/* Port statistics counters. */
+	.get_strings			= lan9645x_get_strings,
+	.get_ethtool_stats		= lan9645x_get_ethtool_stats,
+	.get_sset_count			= lan9645x_get_sset_count,
+	.get_eth_mac_stats		= lan9645x_get_eth_mac_stats,
+	.get_rmon_stats			= lan9645x_get_rmon_stats,
+	.get_stats64			= lan9645x_get_stats64,
+	.get_pause_stats		= lan9645x_get_pause_stats,
+	.get_mm_stats			= lan9645x_get_mm_stats,
Discovered by: opus-5 · Missed by: sashiko-gemini

Is .get_mm_stats reachable without a .get_mm implementation?  A grep over
drivers/net/dsa/microchip/lan9645x/ finds only get_mm_stats, neither
.get_mm nor .set_mm is provided.

dsa_user_get_mm() refuses the request in that case:

net/dsa/user.c:dsa_user_get_mm() {
	if (!ds->ops->get_mm)
		return -EOPNOTSUPP;
	...
}

and mm_prepare_data() leaves before ->get_mm_stats() is invoked:

net/ethtool/mm.c:mm_prepare_data() {
	ret = ops->get_mm(dev, &data->state);
	if (ret)
		goto out_complete;

	if (ops->get_mm_stats && (req_base->flags & ETHTOOL_FLAG_STATS))
		ops->get_mm_stats(dev, &data->stats);
	...
}

So does lan9645x_stats_get_mm_stats() ever run, and does
ethtool --show-mm --include-statistics just return -EOPNOTSUPP?

The same missing .get_mm makes __ethtool_dev_mm_supported() always return
false, and both stats_prepare_data() (net/ethtool/stats.c) and
pause_prepare_data() (net/ethtool/pause.c) reject requests carrying
src == EMAC or src == PMAC on that basis.  Does that leave the
ETHTOOL_MAC_STATS_SRC_EMAC and ETHTOOL_MAC_STATS_SRC_PMAC branches of
lan9645x_stats_get_eth_mac_stats(), lan9645x_stats_get_rmon_stats(),
lan9645x_stats_get_pause_stats(), lan9645x_stats_get_eth_phy_stats() and
lan9645x_stats_get_eth_ctrl_stats() unreachable as well, with only the
default aggregate branch ever executing?
Yes, this is also correct. I will remove the mac merge related code until mac
merge itself lands in the driver.
quoted
+	.get_eth_phy_stats		= lan9645x_get_eth_phy_stats,
+	.get_eth_ctrl_stats		= lan9645x_get_eth_ctrl_stats,
 };
[ ... ]
quoted
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
quoted
new file mode 100644
index 0000000000000..f9c4bcb308991
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
@@ -0,0 +1,935 @@
[ ... ]
quoted
+void lan9645x_stats_get_stats64(struct lan9645x *lan9645x, int port,
+				struct rtnl_link_stats64 *stats)
+{
[ ... ]
quoted
+	stats->rx_packets = c[SCNT_RX_SHORT] +
+			    c[SCNT_RX_FRAG] +
+			    c[SCNT_RX_JABBER] +
+			    c[SCNT_RX_SZ_64] +
+			    c[SCNT_RX_SZ_65_127] +
+			    c[SCNT_RX_SZ_128_255] +
+			    c[SCNT_RX_SZ_256_511] +
+			    c[SCNT_RX_SZ_512_1023] +
+			    c[SCNT_RX_SZ_1024_1526] +
+			    c[SCNT_RX_SZ_JUMBO] +
+			    c[SCNT_RX_LONG] +
+			    c[SCNT_RX_PMAC_SHORT] +
+			    c[SCNT_RX_PMAC_FRAG] +
+			    c[SCNT_RX_PMAC_JABBER] +
+			    c[SCNT_RX_PMAC_SZ_64] +
+			    c[SCNT_RX_PMAC_SZ_65_127] +
+			    c[SCNT_RX_PMAC_SZ_128_255] +
+			    c[SCNT_RX_PMAC_SZ_256_511] +
+			    c[SCNT_RX_PMAC_SZ_512_1023] +
+			    c[SCNT_RX_PMAC_SZ_1024_1526] +
+			    c[SCNT_RX_PMAC_SZ_JUMBO];
Discovered by: opus-5 · Missed by: sashiko-gemini

Is the missing c[SCNT_RX_PMAC_LONG] here intentional?  The eMAC oversize
counter c[SCNT_RX_LONG] is summed into rx_packets, but the pMAC term list
stops at SCNT_RX_PMAC_SZ_JUMBO.  The same asymmetry appears in
stats->rx_dropped below, which also starts from c[SCNT_RX_LONG] alone.
Yes, this is a mistake, I will add it.
Every other term in this function is paired, and rx_errors does include
the pMAC counterpart:

	stats->rx_errors = c[SCNT_RX_SHORT] +
			   ...
			   c[SCNT_RX_PMAC_LONG];

SCNT_RX_PMAC_LONG (offset 0x42) is present in
lan9645x_port_stats_layout[] and is accumulated by
__lan9645x_stats_view_idx_transfer(), so on a port running frame
preemption, would oversize frames on the preemptible MAC show up in
rx_errors while being absent from rx_packets and rx_dropped?
quoted
+
+	stats->multicast = c[SCNT_RX_MC] + c[SCNT_RX_PMAC_MC];
+
+	stats->rx_errors = c[SCNT_RX_SHORT] +
+			   c[SCNT_RX_FRAG] +
+			   c[SCNT_RX_JABBER] +
+			   c[SCNT_RX_CRC] +
+			   c[SCNT_RX_SYMBOL_ERR] +
+			   c[SCNT_RX_LONG] +
+			   c[SCNT_RX_PMAC_SHORT] +
+			   c[SCNT_RX_PMAC_FRAG] +
+			   c[SCNT_RX_PMAC_JABBER] +
+			   c[SCNT_RX_PMAC_CRC] +
+			   c[SCNT_RX_PMAC_SYMBOL_ERR] +
+			   c[SCNT_RX_PMAC_LONG];
+
+	stats->rx_dropped = c[SCNT_RX_LONG] +
+			    c[SCNT_DR_LOCAL] +
+			    c[SCNT_DR_TAIL] +
+			    c[SCNT_RX_CAT_DROP] +
Discovered by: opus-5 · Missed by: sashiko-gemini

Should one oversize frame be reported three times?  c[SCNT_RX_LONG] is
added to rx_packets, to rx_errors and to rx_dropped, so a single frame
appears in all three fields of ip -s link and /proc/net/dev.  The same
applies, twice over, to c[SCNT_RX_SHORT], c[SCNT_RX_FRAG],
c[SCNT_RX_JABBER] and their pMAC twins, which land in both rx_packets and
rx_errors.

The uAPI kernel-doc in include/uapi/linux/if_link.h describes these as
distinct categories:

 * @rx_packets: Number of good packets received by the interface.
 * @rx_errors: Total number of bad packets received on this network device.
 * @rx_dropped: Number of packets received but not processed, e.g. due to
 *	lack of resources or unsupported protocol.

and points length violations at @rx_length_errors, which this driver
leaves at zero.

For comparison, ocelot_port_get_stats64()
(drivers/net/ethernet/mscc/ocelot_stats.c) does fold SHORTS, FRAGMENTS,
JABBERS and LONGS into rx_packets, but it does not populate rx_errors at
all and does not add RX_LONGS to rx_dropped.  Was RX_LONG in rx_dropped
meant to be rx_length_errors instead?
I will add rx_length_errors, and move RX_LONG from rx_dropped to there.
quoted
+			    c[SCNT_RX_RED_PRIO_0] +
[ ... ]
quoted
+			    c[SCNT_RX_RED_PRIO_7];
[ ... ]
quoted
+void lan9645x_stats_init(struct lan9645x *lan9645x)
+{
+	struct lan9645x_stats *stats = lan9645x->stats;
+
+	queue_delayed_work(stats->queue, &stats->work,
+			   LAN9645X_STATS_CHECK_DELAY);
+}
Discovered by: opus-5 · Missed by: sashiko-gemini

Where does the software shadow and the hardware counter get a common
baseline?

lan9645x_stats_add_cnt() keeps the previous hardware value in the low half
of the shadow and infers a wrap from it:

	if (val < (*cnt & U32_MAX))
		*cnt += (u64)1 << 32; /* value has wrapped */

	*cnt = (*cnt & ~(u64)U32_MAX) + val;

vstat->cnts is devm_kcalloc()'d once in lan9645x_stats_alloc() during
probe and never re-zeroed, and SYS_STAT_CFG.STAT_CLEAR_SHOT is defined in
lan9645x_regs.h but never written.  lan9645x_stats_init() runs from
lan9645x_setup() after lan9645x_reset_switch() has driven
SYS_RESET_CFG.CORE_ENA=0 and run SYS_RAM_INIT, and it only re-arms the
poll work.

If the core reset does clear the counter RAM, then on a second .setup()
of an already-probed instance (DSA tree teardown and re-setup, which the
.setup/.teardown stats hooks appear written for) hardware restarts at 0
while the shadow still holds the old values, so the first poll sees
val = 0 < (*cnt & U32_MAX) for every non-zero counter and adds
4294967296 to each, permanently.

If the reset does not clear them, an unbind/rebind starts with a zeroed
shadow against non-zero hardware, so the fresh interface reports traffic
it never saw.

ocelot_init() clears the counters explicitly for the same block:

drivers/net/ethernet/mscc/ocelot.c:ocelot_init() {
		/* Clear all counters (5 groups) */
		ocelot_write(ocelot, SYS_STAT_CFG_STAT_VIEW(port) |
				     SYS_STAT_CFG_STAT_CLEAR_SHOT(0x7f),
			     SYS_STAT_CFG);
}

Would it make sense to clear the hardware counters and/or memset the
shadow in lan9645x_stats_init() so the two always start together?
Yes I think this makes sense, I will add this.
[ ... ]
quoted
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h
b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h
quoted
new file mode 100644
index 0000000000000..cd5db16258a09
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h
@@ -0,0 +1,279 @@
[ ... ]
quoted
+/* Counter indices into stat layout structs */
+#define SCNT_FRER_SID_IN_PKT             0
+#define SCNT_ISDX_GREEN_OCT              1
[ ... ]
quoted
+#define SCNT_SF_MATCHING_FRAMES_COUNT    0
[ ... ]
quoted
+#define SCNT_ESDX_GREEN_OCT              0
[ ... ]
quoted
+enum lan9645x_view_stat_type {
+	LAN9645X_STAT_PORTS = 0,
+	LAN9645X_STAT_ISDX,
+	LAN9645X_STAT_ESDX,
+	LAN9645X_STAT_SFID,
+
+	LAN9645X_STAT_NUM,
+};
Discovered by: opus-5 · Missed by: sashiko-gemini

This isn't a bug, but the ISDX, ESDX, SFID and FRER parts of this
framework have no user in the patch.  A grep over
drivers/net/dsa/microchip/lan9645x/ shows SCNT_ISDX_*, SCNT_SF_*,
SCNT_ESDX_* and SCNT_FRER_SID_IN_PKT matching only in this header, and
lan9645x_view_stat_cfgs[] populates just [LAN9645X_STAT_PORTS].

lan9645x_stats_view_update() has PORTS as its only non-default case, so
the loop in lan9645x_stats_update():

	for (int vtype = 0; vtype < LAN9645X_STAT_NUM; vtype++)
		lan9645x_stats_view_update(lan9645x, vtype);

does three no-op iterations over zeroed view[] entries on every poll.

Also, struct lan9645x_view_stats.name is only ever written
(.name = "ports") and never read, and the back-pointer assignment
vstat->stats = lan9645x->stats in lan9645x_view_stat_init() has no
reader.  Since the commit message only describes adding "statistics
support for the port counters", could the unused infrastructure be
dropped until the view that needs it lands?
Yes I will remove the unused views and fields.
[ ... ]
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help