public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 0/2] net: dsa: mxl862xx: add statistics support
@ 2026-04-11  0:13 Daniel Golle
  2026-04-11  0:13 ` [PATCH net-next 1/2] net: dsa: mxl862xx: add ethtool " Daniel Golle
  2026-04-11  0:13 ` [PATCH net-next 2/2] net: dsa: mxl862xx: implement .get_stats64 Daniel Golle
  0 siblings, 2 replies; 3+ messages in thread
From: Daniel Golle @ 2026-04-11  0:13 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King, netdev,
	linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin

Add per-port RMON statistics support for the MxL862xx DSA driver,
covering hardware-specific ethtool -S counters, standard IEEE 802.3
MAC/ctrl/pause statistics, and rtnl_link_stats64 via polled 64-bit
accumulation.

Daniel Golle (2):
  net: dsa: mxl862xx: add ethtool statistics support
  net: dsa: mxl862xx: implement .get_stats64

 drivers/net/dsa/mxl862xx/mxl862xx-api.h  | 142 ++++++++++
 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h  |   3 +
 drivers/net/dsa/mxl862xx/mxl862xx-host.c |   8 +-
 drivers/net/dsa/mxl862xx/mxl862xx.c      | 325 +++++++++++++++++++++++
 drivers/net/dsa/mxl862xx/mxl862xx.h      |  94 ++++++-
 5 files changed, 565 insertions(+), 7 deletions(-)

-- 
2.53.0

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH net-next 1/2] net: dsa: mxl862xx: add ethtool statistics support
  2026-04-11  0:13 [PATCH net-next 0/2] net: dsa: mxl862xx: add statistics support Daniel Golle
@ 2026-04-11  0:13 ` Daniel Golle
  2026-04-11  0:13 ` [PATCH net-next 2/2] net: dsa: mxl862xx: implement .get_stats64 Daniel Golle
  1 sibling, 0 replies; 3+ messages in thread
From: Daniel Golle @ 2026-04-11  0:13 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King, netdev,
	linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin

The MxL862xx firmware exposes per-port RMON counters through the
RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
(unicast/multicast/broadcast packet and byte counts, collision
counters, pause frames) as well as hardware-specific counters such
as extended VLAN discard and MTU exceed events.

Add the RMON counter firmware API structures and command definitions.
Implement .get_strings, .get_sset_count, and .get_ethtool_stats for
legacy ethtool -S support. Implement .get_eth_mac_stats,
.get_eth_ctrl_stats, and .get_pause_stats for the standardized
IEEE 802.3 statistics interface.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++++
 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |   3 +
 drivers/net/dsa/mxl862xx/mxl862xx.c     | 151 ++++++++++++++++++++++++
 3 files changed, 296 insertions(+)

diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index c902e90397e5f..fb21ddc1bf1c0 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -1224,4 +1224,146 @@ struct mxl862xx_sys_fw_image_version {
 	__le32 iv_build_num;
 } __packed;
 
+/**
+ * enum mxl862xx_port_type - Port Type
+ * @MXL862XX_LOGICAL_PORT: Logical Port
+ * @MXL862XX_PHYSICAL_PORT: Physical Port
+ * @MXL862XX_CTP_PORT: Connectivity Termination Port (CTP)
+ * @MXL862XX_BRIDGE_PORT: Bridge Port
+ */
+enum mxl862xx_port_type {
+	MXL862XX_LOGICAL_PORT = 0,
+	MXL862XX_PHYSICAL_PORT,
+	MXL862XX_CTP_PORT,
+	MXL862XX_BRIDGE_PORT,
+};
+
+/**
+ * enum mxl862xx_rmon_port_type - RMON counter table type
+ * @MXL862XX_RMON_CTP_PORT_RX: CTP RX counters
+ * @MXL862XX_RMON_CTP_PORT_TX: CTP TX counters
+ * @MXL862XX_RMON_BRIDGE_PORT_RX: Bridge port RX counters
+ * @MXL862XX_RMON_BRIDGE_PORT_TX: Bridge port TX counters
+ * @MXL862XX_RMON_CTP_PORT_PCE_BYPASS: CTP PCE bypass counters
+ * @MXL862XX_RMON_TFLOW_RX: TFLOW RX counters
+ * @MXL862XX_RMON_TFLOW_TX: TFLOW TX counters
+ * @MXL862XX_RMON_QMAP: QMAP counters
+ * @MXL862XX_RMON_METER: Meter counters
+ * @MXL862XX_RMON_PMAC: PMAC counters
+ */
+enum mxl862xx_rmon_port_type {
+	MXL862XX_RMON_CTP_PORT_RX = 0,
+	MXL862XX_RMON_CTP_PORT_TX,
+	MXL862XX_RMON_BRIDGE_PORT_RX,
+	MXL862XX_RMON_BRIDGE_PORT_TX,
+	MXL862XX_RMON_CTP_PORT_PCE_BYPASS,
+	MXL862XX_RMON_TFLOW_RX,
+	MXL862XX_RMON_TFLOW_TX,
+	MXL862XX_RMON_QMAP = 0x0e,
+	MXL862XX_RMON_METER = 0x19,
+	MXL862XX_RMON_PMAC = 0x1c,
+};
+
+/**
+ * struct mxl862xx_rmon_port_cnt - RMON counters for a port
+ * @port_type: Port type for counter retrieval (see &enum mxl862xx_port_type)
+ * @port_id: Ethernet port number (zero-based)
+ * @sub_if_id_group: Sub-interface ID group
+ * @pce_bypass: Separate CTP Tx counters when PCE is bypassed
+ * @rx_extended_vlan_discard_pkts: Discarded at extended VLAN operation
+ * @mtu_exceed_discard_pkts: Discarded due to MTU exceeded
+ * @tx_under_size_good_pkts: Tx undersize (<64) packet count
+ * @tx_oversize_good_pkts: Tx oversize (>1518) packet count
+ * @rx_good_pkts: Received good packet count
+ * @rx_unicast_pkts: Received unicast packet count
+ * @rx_broadcast_pkts: Received broadcast packet count
+ * @rx_multicast_pkts: Received multicast packet count
+ * @rx_fcserror_pkts: Received FCS error packet count
+ * @rx_under_size_good_pkts: Received undersize good packet count
+ * @rx_oversize_good_pkts: Received oversize good packet count
+ * @rx_under_size_error_pkts: Received undersize error packet count
+ * @rx_good_pause_pkts: Received good pause packet count
+ * @rx_oversize_error_pkts: Received oversize error packet count
+ * @rx_align_error_pkts: Received alignment error packet count
+ * @rx_filtered_pkts: Filtered packet count
+ * @rx64byte_pkts: Received 64-byte packet count
+ * @rx127byte_pkts: Received 65-127 byte packet count
+ * @rx255byte_pkts: Received 128-255 byte packet count
+ * @rx511byte_pkts: Received 256-511 byte packet count
+ * @rx1023byte_pkts: Received 512-1023 byte packet count
+ * @rx_max_byte_pkts: Received 1024-max byte packet count
+ * @tx_good_pkts: Transmitted good packet count
+ * @tx_unicast_pkts: Transmitted unicast packet count
+ * @tx_broadcast_pkts: Transmitted broadcast packet count
+ * @tx_multicast_pkts: Transmitted multicast packet count
+ * @tx_single_coll_count: Transmit single collision count
+ * @tx_mult_coll_count: Transmit multiple collision count
+ * @tx_late_coll_count: Transmit late collision count
+ * @tx_excess_coll_count: Transmit excessive collision count
+ * @tx_coll_count: Transmit collision count
+ * @tx_pause_count: Transmit pause packet count
+ * @tx64byte_pkts: Transmitted 64-byte packet count
+ * @tx127byte_pkts: Transmitted 65-127 byte packet count
+ * @tx255byte_pkts: Transmitted 128-255 byte packet count
+ * @tx511byte_pkts: Transmitted 256-511 byte packet count
+ * @tx1023byte_pkts: Transmitted 512-1023 byte packet count
+ * @tx_max_byte_pkts: Transmitted 1024-max byte packet count
+ * @tx_dropped_pkts: Transmit dropped packet count
+ * @tx_acm_dropped_pkts: Transmit ACM dropped packet count
+ * @rx_dropped_pkts: Received dropped packet count
+ * @rx_good_bytes: Received good byte count (64-bit)
+ * @rx_bad_bytes: Received bad byte count (64-bit)
+ * @tx_good_bytes: Transmitted good byte count (64-bit)
+ */
+struct mxl862xx_rmon_port_cnt {
+	__le32 port_type; /* enum mxl862xx_port_type */
+	__le16 port_id;
+	__le16 sub_if_id_group;
+	u8 pce_bypass;
+	__le32 rx_extended_vlan_discard_pkts;
+	__le32 mtu_exceed_discard_pkts;
+	__le32 tx_under_size_good_pkts;
+	__le32 tx_oversize_good_pkts;
+	__le32 rx_good_pkts;
+	__le32 rx_unicast_pkts;
+	__le32 rx_broadcast_pkts;
+	__le32 rx_multicast_pkts;
+	__le32 rx_fcserror_pkts;
+	__le32 rx_under_size_good_pkts;
+	__le32 rx_oversize_good_pkts;
+	__le32 rx_under_size_error_pkts;
+	__le32 rx_good_pause_pkts;
+	__le32 rx_oversize_error_pkts;
+	__le32 rx_align_error_pkts;
+	__le32 rx_filtered_pkts;
+	__le32 rx64byte_pkts;
+	__le32 rx127byte_pkts;
+	__le32 rx255byte_pkts;
+	__le32 rx511byte_pkts;
+	__le32 rx1023byte_pkts;
+	__le32 rx_max_byte_pkts;
+	__le32 tx_good_pkts;
+	__le32 tx_unicast_pkts;
+	__le32 tx_broadcast_pkts;
+	__le32 tx_multicast_pkts;
+	__le32 tx_single_coll_count;
+	__le32 tx_mult_coll_count;
+	__le32 tx_late_coll_count;
+	__le32 tx_excess_coll_count;
+	__le32 tx_coll_count;
+	__le32 tx_pause_count;
+	__le32 tx64byte_pkts;
+	__le32 tx127byte_pkts;
+	__le32 tx255byte_pkts;
+	__le32 tx511byte_pkts;
+	__le32 tx1023byte_pkts;
+	__le32 tx_max_byte_pkts;
+	__le32 tx_dropped_pkts;
+	__le32 tx_acm_dropped_pkts;
+	__le32 rx_dropped_pkts;
+	__le64 rx_good_bytes;
+	__le64 rx_bad_bytes;
+	__le64 tx_good_bytes;
+} __packed;
+
 #endif /* __MXL862XX_API_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index 45df37cde40d1..f1ea40aa7ea08 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -16,6 +16,7 @@
 #define MXL862XX_BRDGPORT_MAGIC		0x400
 #define MXL862XX_CTP_MAGIC		0x500
 #define MXL862XX_QOS_MAGIC		0x600
+#define MXL862XX_RMON_MAGIC		0x700
 #define MXL862XX_SWMAC_MAGIC		0xa00
 #define MXL862XX_EXTVLAN_MAGIC		0xb00
 #define MXL862XX_VLANFILTER_MAGIC	0xc00
@@ -43,6 +44,8 @@
 #define MXL862XX_QOS_METERCFGSET	(MXL862XX_QOS_MAGIC + 0x2)
 #define MXL862XX_QOS_METERALLOC		(MXL862XX_QOS_MAGIC + 0x2a)
 
+#define MXL862XX_RMON_PORT_GET		(MXL862XX_RMON_MAGIC + 0x1)
+
 #define MXL862XX_MAC_TABLEENTRYADD	(MXL862XX_SWMAC_MAGIC + 0x2)
 #define MXL862XX_MAC_TABLEENTRYREAD	(MXL862XX_SWMAC_MAGIC + 0x3)
 #define MXL862XX_MAC_TABLEENTRYQUERY	(MXL862XX_SWMAC_MAGIC + 0x4)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index fca9a3e36bb69..8159f6a66d724 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -30,6 +30,47 @@
 #define MXL862XX_API_READ_QUIET(dev, cmd, data) \
 	mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
 
+struct mxl862xx_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+};
+
+#define MIB_DESC(_size, _name, _element)					\
+{									\
+	.size = _size,							\
+	.name = _name,							\
+	.offset = offsetof(struct mxl862xx_rmon_port_cnt, _element)	\
+}
+
+static const struct mxl862xx_mib_desc mxl862xx_mib[] = {
+	MIB_DESC(1, "TxUnicastPkts", tx_unicast_pkts),
+	MIB_DESC(1, "Tx64BytePkts", tx64byte_pkts),
+	MIB_DESC(1, "Tx127BytePkts", tx127byte_pkts),
+	MIB_DESC(1, "Tx255BytePkts", tx255byte_pkts),
+	MIB_DESC(1, "Tx511BytePkts", tx511byte_pkts),
+	MIB_DESC(1, "Tx1023BytePkts", tx1023byte_pkts),
+	MIB_DESC(1, "TxMaxBytePkts", tx_max_byte_pkts),
+	MIB_DESC(1, "TxDroppedPkts", tx_dropped_pkts),
+	MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts),
+	MIB_DESC(1, "TxCollCount", tx_coll_count),
+	MIB_DESC(1, "RxUnicastPkts", rx_unicast_pkts),
+	MIB_DESC(1, "RxUnderSizeGoodPkts", rx_under_size_good_pkts),
+	MIB_DESC(1, "RxOversizeGoodPkts", rx_oversize_good_pkts),
+	MIB_DESC(1, "RxUnderSizeErrorPkts", rx_under_size_error_pkts),
+	MIB_DESC(1, "RxFilteredPkts", rx_filtered_pkts),
+	MIB_DESC(1, "Rx64BytePkts", rx64byte_pkts),
+	MIB_DESC(1, "Rx127BytePkts", rx127byte_pkts),
+	MIB_DESC(1, "Rx255BytePkts", rx255byte_pkts),
+	MIB_DESC(1, "Rx511BytePkts", rx511byte_pkts),
+	MIB_DESC(1, "Rx1023BytePkts", rx1023byte_pkts),
+	MIB_DESC(1, "RxMaxBytePkts", rx_max_byte_pkts),
+	MIB_DESC(1, "RxDroppedPkts", rx_dropped_pkts),
+	MIB_DESC(1, "RxExtendedVlanDiscardPkts", rx_extended_vlan_discard_pkts),
+	MIB_DESC(1, "MtuExceedDiscardPkts", mtu_exceed_discard_pkts),
+	MIB_DESC(2, "RxBadBytes", rx_bad_bytes),
+};
+
 #define MXL862XX_SDMA_PCTRLP(p)		(0xbc0 + ((p) * 0x6))
 #define MXL862XX_SDMA_PCTRL_EN		BIT(0)
 
@@ -1734,6 +1775,110 @@ static int mxl862xx_port_bridge_flags(struct dsa_switch *ds, int port,
 	return 0;
 }
 
+static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
+				 u32 stringset, u8 *data)
+{
+	int i;
+
+	if (stringset != ETH_SS_STATS)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
+		ethtool_puts(&data, mxl862xx_mib[i].name);
+}
+
+static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+	if (sset != ETH_SS_STATS)
+		return 0;
+
+	return ARRAY_SIZE(mxl862xx_mib);
+}
+
+static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
+			      struct mxl862xx_rmon_port_cnt *cnt)
+{
+	memset(cnt, 0, sizeof(*cnt));
+	cnt->port_type = cpu_to_le32(MXL862XX_CTP_PORT);
+	cnt->port_id = cpu_to_le16(port);
+
+	return MXL862XX_API_READ(ds->priv, MXL862XX_RMON_PORT_GET, *cnt);
+}
+
+static void mxl862xx_get_ethtool_stats(struct dsa_switch *ds, int port,
+				       u64 *data)
+{
+	const struct mxl862xx_mib_desc *mib;
+	struct mxl862xx_rmon_port_cnt cnt;
+	int ret, i;
+	void *field;
+
+	ret = mxl862xx_read_rmon(ds, port, &cnt);
+	if (ret) {
+		dev_err(ds->dev, "failed to read RMON stats on port %d\n", port);
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++) {
+		mib = &mxl862xx_mib[i];
+		field = (u8 *)&cnt + mib->offset;
+
+		if (mib->size == 1)
+			*data++ = le32_to_cpu(*(__le32 *)field);
+		else
+			*data++ = le64_to_cpu(*(__le64 *)field);
+	}
+}
+
+static void mxl862xx_get_eth_mac_stats(struct dsa_switch *ds, int port,
+				       struct ethtool_eth_mac_stats *mac_stats)
+{
+	struct mxl862xx_rmon_port_cnt cnt;
+
+	if (mxl862xx_read_rmon(ds, port, &cnt))
+		return;
+
+	mac_stats->FramesTransmittedOK = le32_to_cpu(cnt.tx_good_pkts);
+	mac_stats->SingleCollisionFrames = le32_to_cpu(cnt.tx_single_coll_count);
+	mac_stats->MultipleCollisionFrames = le32_to_cpu(cnt.tx_mult_coll_count);
+	mac_stats->FramesReceivedOK = le32_to_cpu(cnt.rx_good_pkts);
+	mac_stats->FrameCheckSequenceErrors = le32_to_cpu(cnt.rx_fcserror_pkts);
+	mac_stats->AlignmentErrors = le32_to_cpu(cnt.rx_align_error_pkts);
+	mac_stats->OctetsTransmittedOK = le64_to_cpu(cnt.tx_good_bytes);
+	mac_stats->LateCollisions = le32_to_cpu(cnt.tx_late_coll_count);
+	mac_stats->FramesAbortedDueToXSColls = le32_to_cpu(cnt.tx_excess_coll_count);
+	mac_stats->OctetsReceivedOK = le64_to_cpu(cnt.rx_good_bytes);
+	mac_stats->MulticastFramesXmittedOK = le32_to_cpu(cnt.tx_multicast_pkts);
+	mac_stats->BroadcastFramesXmittedOK = le32_to_cpu(cnt.tx_broadcast_pkts);
+	mac_stats->MulticastFramesReceivedOK = le32_to_cpu(cnt.rx_multicast_pkts);
+	mac_stats->BroadcastFramesReceivedOK = le32_to_cpu(cnt.rx_broadcast_pkts);
+	mac_stats->FrameTooLongErrors = le32_to_cpu(cnt.rx_oversize_error_pkts);
+}
+
+static void mxl862xx_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+					struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+	struct mxl862xx_rmon_port_cnt cnt;
+
+	if (mxl862xx_read_rmon(ds, port, &cnt))
+		return;
+
+	ctrl_stats->MACControlFramesTransmitted = le32_to_cpu(cnt.tx_pause_count);
+	ctrl_stats->MACControlFramesReceived = le32_to_cpu(cnt.rx_good_pause_pkts);
+}
+
+static void mxl862xx_get_pause_stats(struct dsa_switch *ds, int port,
+				     struct ethtool_pause_stats *pause_stats)
+{
+	struct mxl862xx_rmon_port_cnt cnt;
+
+	if (mxl862xx_read_rmon(ds, port, &cnt))
+		return;
+
+	pause_stats->tx_pause_frames = le32_to_cpu(cnt.tx_pause_count);
+	pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
+}
+
 static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.get_tag_protocol = mxl862xx_get_tag_protocol,
 	.setup = mxl862xx_setup,
@@ -1758,6 +1903,12 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.port_vlan_filtering = mxl862xx_port_vlan_filtering,
 	.port_vlan_add = mxl862xx_port_vlan_add,
 	.port_vlan_del = mxl862xx_port_vlan_del,
+	.get_strings = mxl862xx_get_strings,
+	.get_sset_count = mxl862xx_get_sset_count,
+	.get_ethtool_stats = mxl862xx_get_ethtool_stats,
+	.get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+	.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+	.get_pause_stats = mxl862xx_get_pause_stats,
 };
 
 static void mxl862xx_phylink_mac_config(struct phylink_config *config,
-- 
2.53.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH net-next 2/2] net: dsa: mxl862xx: implement .get_stats64
  2026-04-11  0:13 [PATCH net-next 0/2] net: dsa: mxl862xx: add statistics support Daniel Golle
  2026-04-11  0:13 ` [PATCH net-next 1/2] net: dsa: mxl862xx: add ethtool " Daniel Golle
@ 2026-04-11  0:13 ` Daniel Golle
  1 sibling, 0 replies; 3+ messages in thread
From: Daniel Golle @ 2026-04-11  0:13 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King, netdev,
	linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin

Poll free-running firmware RMON counters every 2 seconds and accumulate
deltas into 64-bit per-port statistics. 32-bit packet counters wrap
in ~220s at 10 Gbps line rate with minimum-size frames; the 2s polling
interval provides a comfortable margin. The .get_stats64 callback
forces a fresh poll so that counters are always up to date when queried.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/net/dsa/mxl862xx/mxl862xx-host.c |   8 +-
 drivers/net/dsa/mxl862xx/mxl862xx.c      | 174 +++++++++++++++++++++++
 drivers/net/dsa/mxl862xx/mxl862xx.h      |  94 +++++++++++-
 3 files changed, 269 insertions(+), 7 deletions(-)

diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index cadbdb590cf43..d55f9dff6433e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -48,7 +48,7 @@ static void mxl862xx_crc_err_work_fn(struct work_struct *work)
 		dev_close(dp->conduit);
 	rtnl_unlock();
 
-	clear_bit(0, &priv->crc_err);
+	clear_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags);
 }
 
 /* Firmware CRC error codes (outside normal Zephyr errno range). */
@@ -247,7 +247,7 @@ static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)
 
 	ret = mxl862xx_crc6_verify(ctrl_enc, len_enc, &fw_result);
 	if (ret) {
-		if (!test_and_set_bit(0, &priv->crc_err))
+		if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
 			schedule_work(&priv->crc_err_work);
 		return -EIO;
 	}
@@ -314,7 +314,7 @@ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
 	if (ret < 0) {
 		if ((ret == MXL862XX_FW_CRC6_ERR ||
 		     ret == MXL862XX_FW_CRC16_ERR) &&
-		    !test_and_set_bit(0, &priv->crc_err))
+		    !test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
 			schedule_work(&priv->crc_err_work);
 		if (!quiet)
 			dev_err(&priv->mdiodev->dev,
@@ -458,7 +458,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
 	}
 
 	if (crc16(0xffff, (const u8 *)data, size) != crc) {
-		if (!test_and_set_bit(0, &priv->crc_err))
+		if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
 			schedule_work(&priv->crc_err_work);
 		ret = -EIO;
 		goto out;
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 8159f6a66d724..01b3ebd62231a 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -30,6 +30,12 @@
 #define MXL862XX_API_READ_QUIET(dev, cmd, data) \
 	mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
 
+/* Polling interval for RMON counter accumulation. At 2.5 Gbps with
+ * minimum-size (64-byte) frames, a 32-bit packet counter wraps in ~880s.
+ * 2s gives a comfortable margin.
+ */
+#define MXL862XX_STATS_POLL_INTERVAL	(2 * HZ)
+
 struct mxl862xx_mib_desc {
 	unsigned int size;
 	unsigned int offset;
@@ -686,6 +692,9 @@ static int mxl862xx_setup(struct dsa_switch *ds)
 	if (ret)
 		return ret;
 
+	schedule_delayed_work(&priv->stats_work,
+			      MXL862XX_STATS_POLL_INTERVAL);
+
 	return mxl862xx_setup_mdio(ds);
 }
 
@@ -1879,6 +1888,158 @@ static void mxl862xx_get_pause_stats(struct dsa_switch *ds, int port,
 	pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
 }
 
+/* Compute the delta between two 32-bit free-running counter snapshots,
+ * handling a single wrap-around correctly via unsigned subtraction.
+ */
+static u64 mxl862xx_delta32(u32 cur, u32 prev)
+{
+	return (u32)(cur - prev);
+}
+
+/**
+ * mxl862xx_stats_poll - Read RMON counters and accumulate into 64-bit stats
+ * @ds: DSA switch
+ * @port: port index
+ *
+ * The firmware RMON counters are free-running 32-bit values (64-bit for
+ * byte counters). This function reads the hardware via MDIO (may sleep),
+ * computes deltas from the previous snapshot, and accumulates them into
+ * 64-bit per-port stats under a spinlock.
+ *
+ * Called only from the stats polling workqueue -- serialized by the
+ * single-threaded delayed_work, so no MDIO locking is needed here.
+ */
+static void mxl862xx_stats_poll(struct dsa_switch *ds, int port)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_port_stats *s = &priv->ports[port].stats;
+	u32 rx_fcserr, rx_under, rx_over, rx_align, tx_drop;
+	u32 rx_drop, rx_evlan, mtu_exc, tx_acm;
+	struct mxl862xx_rmon_port_cnt cnt;
+	u64 rx_bytes, tx_bytes;
+	u32 rx_mcast, tx_coll;
+	u32 rx_pkts, tx_pkts;
+
+	/* MDIO read -- may sleep, done outside the spinlock. */
+	if (mxl862xx_read_rmon(ds, port, &cnt))
+		return;
+
+	rx_pkts   = le32_to_cpu(cnt.rx_good_pkts);
+	tx_pkts   = le32_to_cpu(cnt.tx_good_pkts);
+	rx_bytes  = le64_to_cpu(cnt.rx_good_bytes);
+	tx_bytes  = le64_to_cpu(cnt.tx_good_bytes);
+	rx_fcserr = le32_to_cpu(cnt.rx_fcserror_pkts);
+	rx_under  = le32_to_cpu(cnt.rx_under_size_error_pkts);
+	rx_over   = le32_to_cpu(cnt.rx_oversize_error_pkts);
+	rx_align  = le32_to_cpu(cnt.rx_align_error_pkts);
+	tx_drop   = le32_to_cpu(cnt.tx_dropped_pkts);
+	rx_drop   = le32_to_cpu(cnt.rx_dropped_pkts);
+	rx_evlan  = le32_to_cpu(cnt.rx_extended_vlan_discard_pkts);
+	mtu_exc   = le32_to_cpu(cnt.mtu_exceed_discard_pkts);
+	tx_acm    = le32_to_cpu(cnt.tx_acm_dropped_pkts);
+	rx_mcast  = le32_to_cpu(cnt.rx_multicast_pkts);
+	tx_coll   = le32_to_cpu(cnt.tx_coll_count);
+
+	/* Accumulate deltas under spinlock -- .get_stats64 reads these. */
+	spin_lock_bh(&priv->ports[port].stats_lock);
+
+	s->rx_packets += mxl862xx_delta32(rx_pkts, s->prev_rx_good_pkts);
+	s->tx_packets += mxl862xx_delta32(tx_pkts, s->prev_tx_good_pkts);
+	s->rx_bytes   += rx_bytes - s->prev_rx_good_bytes;
+	s->tx_bytes   += tx_bytes - s->prev_tx_good_bytes;
+
+	s->rx_errors +=
+		mxl862xx_delta32(rx_fcserr, s->prev_rx_fcserror_pkts) +
+		mxl862xx_delta32(rx_under, s->prev_rx_under_size_error_pkts) +
+		mxl862xx_delta32(rx_over, s->prev_rx_oversize_error_pkts) +
+		mxl862xx_delta32(rx_align, s->prev_rx_align_error_pkts);
+	s->tx_errors +=
+		mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts);
+
+	s->rx_dropped +=
+		mxl862xx_delta32(rx_drop, s->prev_rx_dropped_pkts) +
+		mxl862xx_delta32(rx_evlan, s->prev_rx_evlan_discard_pkts) +
+		mxl862xx_delta32(mtu_exc, s->prev_mtu_exceed_discard_pkts);
+	s->tx_dropped +=
+		mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts) +
+		mxl862xx_delta32(tx_acm, s->prev_tx_acm_dropped_pkts);
+
+	s->multicast  += mxl862xx_delta32(rx_mcast, s->prev_rx_multicast_pkts);
+	s->collisions += mxl862xx_delta32(tx_coll, s->prev_tx_coll_count);
+
+	s->rx_length_errors +=
+		mxl862xx_delta32(rx_under, s->prev_rx_under_size_error_pkts) +
+		mxl862xx_delta32(rx_over, s->prev_rx_oversize_error_pkts);
+	s->rx_crc_errors +=
+		mxl862xx_delta32(rx_fcserr, s->prev_rx_fcserror_pkts);
+	s->rx_frame_errors +=
+		mxl862xx_delta32(rx_align, s->prev_rx_align_error_pkts);
+
+	s->prev_rx_good_pkts             = rx_pkts;
+	s->prev_tx_good_pkts             = tx_pkts;
+	s->prev_rx_good_bytes            = rx_bytes;
+	s->prev_tx_good_bytes            = tx_bytes;
+	s->prev_rx_fcserror_pkts         = rx_fcserr;
+	s->prev_rx_under_size_error_pkts = rx_under;
+	s->prev_rx_oversize_error_pkts   = rx_over;
+	s->prev_rx_align_error_pkts      = rx_align;
+	s->prev_tx_dropped_pkts          = tx_drop;
+	s->prev_rx_dropped_pkts          = rx_drop;
+	s->prev_rx_evlan_discard_pkts    = rx_evlan;
+	s->prev_mtu_exceed_discard_pkts  = mtu_exc;
+	s->prev_tx_acm_dropped_pkts      = tx_acm;
+	s->prev_rx_multicast_pkts        = rx_mcast;
+	s->prev_tx_coll_count            = tx_coll;
+
+	spin_unlock_bh(&priv->ports[port].stats_lock);
+}
+
+static void mxl862xx_stats_work_fn(struct work_struct *work)
+{
+	struct mxl862xx_priv *priv =
+		container_of(work, struct mxl862xx_priv, stats_work.work);
+	struct dsa_switch *ds = priv->ds;
+	struct dsa_port *dp;
+
+	dsa_switch_for_each_available_port(dp, ds)
+		mxl862xx_stats_poll(ds, dp->index);
+
+	if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+		schedule_delayed_work(&priv->stats_work,
+				      MXL862XX_STATS_POLL_INTERVAL);
+}
+
+static void mxl862xx_get_stats64(struct dsa_switch *ds, int port,
+				 struct rtnl_link_stats64 *s)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_port_stats *ps = &priv->ports[port].stats;
+
+	spin_lock_bh(&priv->ports[port].stats_lock);
+
+	s->rx_packets = ps->rx_packets;
+	s->tx_packets = ps->tx_packets;
+	s->rx_bytes = ps->rx_bytes;
+	s->tx_bytes = ps->tx_bytes;
+	s->rx_errors = ps->rx_errors;
+	s->tx_errors = ps->tx_errors;
+	s->rx_dropped = ps->rx_dropped;
+	s->tx_dropped = ps->tx_dropped;
+	s->multicast = ps->multicast;
+	s->collisions = ps->collisions;
+	s->rx_length_errors = ps->rx_length_errors;
+	s->rx_crc_errors = ps->rx_crc_errors;
+	s->rx_frame_errors = ps->rx_frame_errors;
+
+	spin_unlock_bh(&priv->ports[port].stats_lock);
+
+	/* Trigger a fresh poll so the next read sees up-to-date counters.
+	 * No-op if the work is already pending, running, or teardown started.
+	 */
+	if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags))
+		schedule_delayed_work(&priv->stats_work, 0);
+}
+
 static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.get_tag_protocol = mxl862xx_get_tag_protocol,
 	.setup = mxl862xx_setup,
@@ -1909,6 +2070,7 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
 	.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
 	.get_pause_stats = mxl862xx_get_pause_stats,
+	.get_stats64 = mxl862xx_get_stats64,
 };
 
 static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -1970,16 +2132,22 @@ static int mxl862xx_probe(struct mdio_device *mdiodev)
 		priv->ports[i].priv = priv;
 		INIT_WORK(&priv->ports[i].host_flood_work,
 			  mxl862xx_host_flood_work_fn);
+		spin_lock_init(&priv->ports[i].stats_lock);
 	}
 
+	INIT_DELAYED_WORK(&priv->stats_work, mxl862xx_stats_work_fn);
+
 	dev_set_drvdata(dev, ds);
 
 	err = dsa_register_switch(ds);
 	if (err) {
+		set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
+		cancel_delayed_work_sync(&priv->stats_work);
 		mxl862xx_host_shutdown(priv);
 		for (i = 0; i < MXL862XX_MAX_PORTS; i++)
 			cancel_work_sync(&priv->ports[i].host_flood_work);
 	}
+
 	return err;
 }
 
@@ -1994,6 +2162,9 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
 
 	priv = ds->priv;
 
+	set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
+	cancel_delayed_work_sync(&priv->stats_work);
+
 	dsa_unregister_switch(ds);
 
 	mxl862xx_host_shutdown(priv);
@@ -2020,6 +2191,9 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
 
 	dsa_switch_shutdown(ds);
 
+	set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
+	cancel_delayed_work_sync(&priv->stats_work);
+
 	mxl862xx_host_shutdown(priv);
 
 	for (i = 0; i < MXL862XX_MAX_PORTS; i++)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index a010cf6b961a9..80053ab40e4ce 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -116,6 +116,79 @@ struct mxl862xx_evlan_block {
 	u16 n_active;
 };
 
+/**
+ * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
+ * @rx_packets: total received packets
+ * @tx_packets: total transmitted packets
+ * @rx_bytes: total received bytes
+ * @tx_bytes: total transmitted bytes
+ * @rx_errors: total receive errors
+ * @tx_errors: total transmit errors
+ * @rx_dropped: total received packets dropped
+ * @tx_dropped: total transmitted packets dropped
+ * @multicast: total received multicast packets
+ * @collisions: total transmit collisions
+ * @rx_length_errors: received length errors (undersize + oversize)
+ * @rx_crc_errors: received FCS errors
+ * @rx_frame_errors: received alignment errors
+ * @prev_rx_good_pkts: previous snapshot of rx good packet counter
+ * @prev_tx_good_pkts: previous snapshot of tx good packet counter
+ * @prev_rx_good_bytes: previous snapshot of rx good byte counter
+ * @prev_tx_good_bytes: previous snapshot of tx good byte counter
+ * @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
+ * @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
+ *                                 error counter
+ * @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
+ *                               error counter
+ * @prev_rx_align_error_pkts: previous snapshot of rx alignment
+ *                            error counter
+ * @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
+ * @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
+ * @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
+ *                              discard counter
+ * @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
+ *                                discard counter
+ * @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
+ *                            counter
+ * @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
+ * @prev_tx_coll_count: previous snapshot of tx collision counter
+ *
+ * The firmware RMON counters are 32-bit free-running (64-bit for byte
+ * counters). This structure holds 64-bit accumulators alongside the
+ * previous raw snapshot so that deltas can be computed across polls,
+ * handling 32-bit wrap correctly via unsigned subtraction.
+ */
+struct mxl862xx_port_stats {
+	u64 rx_packets;
+	u64 tx_packets;
+	u64 rx_bytes;
+	u64 tx_bytes;
+	u64 rx_errors;
+	u64 tx_errors;
+	u64 rx_dropped;
+	u64 tx_dropped;
+	u64 multicast;
+	u64 collisions;
+	u64 rx_length_errors;
+	u64 rx_crc_errors;
+	u64 rx_frame_errors;
+	u32 prev_rx_good_pkts;
+	u32 prev_tx_good_pkts;
+	u64 prev_rx_good_bytes;
+	u64 prev_tx_good_bytes;
+	u32 prev_rx_fcserror_pkts;
+	u32 prev_rx_under_size_error_pkts;
+	u32 prev_rx_oversize_error_pkts;
+	u32 prev_rx_align_error_pkts;
+	u32 prev_tx_dropped_pkts;
+	u32 prev_rx_dropped_pkts;
+	u32 prev_rx_evlan_discard_pkts;
+	u32 prev_mtu_exceed_discard_pkts;
+	u32 prev_tx_acm_dropped_pkts;
+	u32 prev_rx_multicast_pkts;
+	u32 prev_tx_coll_count;
+};
+
 /**
  * struct mxl862xx_port - per-port state tracked by the driver
  * @priv:                back-pointer to switch private data; needed by
@@ -145,6 +218,10 @@ struct mxl862xx_evlan_block {
  *                       The worker acquires rtnl_lock() to serialize with
  *                       DSA callbacks and checks @setup_done to avoid
  *                       acting on torn-down ports.
+ * @stats:               64-bit accumulated hardware statistics; updated
+ *                       periodically by the stats polling work
+ * @stats_lock:          protects accumulator reads in .get_stats64 against
+ *                       concurrent updates from the polling work
  */
 struct mxl862xx_port {
 	struct mxl862xx_priv *priv;
@@ -160,16 +237,24 @@ struct mxl862xx_port {
 	bool host_flood_uc;
 	bool host_flood_mc;
 	struct work_struct host_flood_work;
+	struct mxl862xx_port_stats stats;
+	spinlock_t stats_lock; /* protects stats accumulators */
 };
 
+/* Bit indices for struct mxl862xx_priv::flags */
+#define MXL862XX_FLAG_CRC_ERR		0
+#define MXL862XX_FLAG_WORK_STOPPED	1
+
 /**
  * struct mxl862xx_priv - driver private data for an MxL862xx switch
  * @ds:                 pointer to the DSA switch instance
  * @mdiodev:            MDIO device used to communicate with the switch firmware
  * @crc_err_work:       deferred work for shutting down all ports on MDIO CRC
  *                      errors
- * @crc_err:            set atomically before CRC-triggered shutdown, cleared
- *                      after
+ * @flags:              atomic status flags; %MXL862XX_FLAG_CRC_ERR is set
+ *                      before CRC-triggered shutdown and cleared after;
+ *                      %MXL862XX_FLAG_WORK_STOPPED is set before cancelling
+ *                      stats_work to prevent rescheduling during teardown
  * @drop_meter:         index of the single shared zero-rate firmware meter
  *                      used to unconditionally drop traffic (used to block
  *                      flooding)
@@ -181,18 +266,21 @@ struct mxl862xx_port {
  * @evlan_ingress_size: per-port ingress Extended VLAN block size
  * @evlan_egress_size:  per-port egress Extended VLAN block size
  * @vf_block_size:      per-port VLAN Filter block size
+ * @stats_work:         periodic work item that polls RMON hardware counters
+ *                      and accumulates them into 64-bit per-port stats
  */
 struct mxl862xx_priv {
 	struct dsa_switch *ds;
 	struct mdio_device *mdiodev;
 	struct work_struct crc_err_work;
-	unsigned long crc_err;
+	unsigned long flags;
 	u16 drop_meter;
 	struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
 	u16 bridges[MXL862XX_MAX_BRIDGES + 1];
 	u16 evlan_ingress_size;
 	u16 evlan_egress_size;
 	u16 vf_block_size;
+	struct delayed_work stats_work;
 };
 
 #endif /* __MXL862XX_H */
-- 
2.53.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-04-11  0:13 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-11  0:13 [PATCH net-next 0/2] net: dsa: mxl862xx: add statistics support Daniel Golle
2026-04-11  0:13 ` [PATCH net-next 1/2] net: dsa: mxl862xx: add ethtool " Daniel Golle
2026-04-11  0:13 ` [PATCH net-next 2/2] net: dsa: mxl862xx: implement .get_stats64 Daniel Golle

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox