From nobody Mon Jun 15 15:20:45 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CD99439FD4; Sun, 12 Apr 2026 00:02:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775952126; cv=none; b=ZuXMyEmEhaM3X9Rg1XflJMqRt8+ZqYJHk83c9yJgbhxqGu0JmxKZgMKBR1H1+z1Es9+kPk51vLGrQgnEAY/kJkJE9BLidXgxhFiRrbVvRAqYptXco9PhazCRLugaCbxk0wFUSkTw4LIXm9jGRrgiBxRTj9NRIoSXcwuJi/fGfhU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775952126; c=relaxed/simple; bh=SN/m7kI3Rr26f+axkooFet+ZnH8EoDZJkkP/K4O9v4c=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=RKNKH8mqDBbK1WeXPFMWBXOi8A0hu4mrtDjwpkILyB82HhiTjw45F/uJZuv6yviIXeVfEVZGp4MNuNc1JeI68E+pUMh4pWiDupXZdYzyPgJcfrCkjMe3wJBB2XT7xM30sFkacrHKGcZr0duHFvyaB3eWpm1lZwxBPHyQUvgjUog= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1wBiH2-000000005aJ-2hIm; Sun, 12 Apr 2026 00:02:00 +0000 Date: Sun, 12 Apr 2026 01:01:57 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v2 1/2] net: dsa: mxl862xx: add ethtool statistics support Message-ID: <480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org> References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- v2: * trim mxl862xx_mib[] to counters not covered elsewhere only * remove histogram counters (moved to .get_rmon_stats) * remove RMON error counters (moved to .get_rmon_stats) * remove counters already in .get_eth_mac_stats * remove counters already in .get_stats64 * add mxl862xx_rmon_ranges[] and mxl862xx_get_rmon_stats() drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 +++++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 3 + drivers/net/dsa/mxl862xx/mxl862xx.c | 173 ++++++++++++++++++++++++ 3 files changed, 318 insertions(+) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl8= 62xx/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; =20 +/** + * 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 =3D 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 =3D 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 =3D 0x0e, + MXL862XX_RMON_METER =3D 0x19, + MXL862XX_RMON_PMAC =3D 0x1c, +}; + +/** + * struct mxl862xx_rmon_port_cnt - RMON counters for a port + * @port_type: Port type for counter retrieval (see &enum mxl862xx_port_ty= pe) + * @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/mxl8= 62xx/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) =20 +#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..58bf7210c6d40 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -30,6 +30,38 @@ #define MXL862XX_API_READ_QUIET(dev, cmd, data) \ mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true) =20 +struct mxl862xx_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +#define MIB_DESC(_size, _name, _element) \ +{ \ + .size =3D _size, \ + .name =3D _name, \ + .offset =3D offsetof(struct mxl862xx_rmon_port_cnt, _element) \ +} + +/* Hardware-specific counters not covered by any standardized stats callba= ck. */ +static const struct mxl862xx_mib_desc mxl862xx_mib[] =3D { + MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts), + MIB_DESC(1, "RxFilteredPkts", rx_filtered_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), +}; + +static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] =3D { + { 0, 64 }, + { 65, 127 }, + { 128, 255 }, + { 256, 511 }, + { 512, 1023 }, + { 1024, 10240 }, + {} +}; + #define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6)) #define MXL862XX_SDMA_PCTRL_EN BIT(0) =20 @@ -1734,6 +1766,140 @@ static int mxl862xx_port_bridge_flags(struct dsa_sw= itch *ds, int port, return 0; } =20 +static void mxl862xx_get_strings(struct dsa_switch *ds, int port, + u32 stringset, u8 *data) +{ + int i; + + if (stringset !=3D ETH_SS_STATS) + return; + + for (i =3D 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 ss= et) +{ + if (sset !=3D 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 =3D cpu_to_le32(MXL862XX_CTP_PORT); + cnt->port_id =3D 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 =3D 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 =3D 0; i < ARRAY_SIZE(mxl862xx_mib); i++) { + mib =3D &mxl862xx_mib[i]; + field =3D (u8 *)&cnt + mib->offset; + + if (mib->size =3D=3D 1) + *data++ =3D le32_to_cpu(*(__le32 *)field); + else + *data++ =3D 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 =3D le32_to_cpu(cnt.tx_good_pkts); + mac_stats->SingleCollisionFrames =3D le32_to_cpu(cnt.tx_single_coll_count= ); + mac_stats->MultipleCollisionFrames =3D le32_to_cpu(cnt.tx_mult_coll_count= ); + mac_stats->FramesReceivedOK =3D le32_to_cpu(cnt.rx_good_pkts); + mac_stats->FrameCheckSequenceErrors =3D le32_to_cpu(cnt.rx_fcserror_pkts); + mac_stats->AlignmentErrors =3D le32_to_cpu(cnt.rx_align_error_pkts); + mac_stats->OctetsTransmittedOK =3D le64_to_cpu(cnt.tx_good_bytes); + mac_stats->LateCollisions =3D le32_to_cpu(cnt.tx_late_coll_count); + mac_stats->FramesAbortedDueToXSColls =3D le32_to_cpu(cnt.tx_excess_coll_c= ount); + mac_stats->OctetsReceivedOK =3D le64_to_cpu(cnt.rx_good_bytes); + mac_stats->MulticastFramesXmittedOK =3D le32_to_cpu(cnt.tx_multicast_pkts= ); + mac_stats->BroadcastFramesXmittedOK =3D le32_to_cpu(cnt.tx_broadcast_pkts= ); + mac_stats->MulticastFramesReceivedOK =3D le32_to_cpu(cnt.rx_multicast_pkt= s); + mac_stats->BroadcastFramesReceivedOK =3D le32_to_cpu(cnt.rx_broadcast_pkt= s); + mac_stats->FrameTooLongErrors =3D 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 =3D le32_to_cpu(cnt.tx_pause_coun= t); + ctrl_stats->MACControlFramesReceived =3D le32_to_cpu(cnt.rx_good_pause_pk= ts); +} + +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 =3D le32_to_cpu(cnt.tx_pause_count); + pause_stats->rx_pause_frames =3D le32_to_cpu(cnt.rx_good_pause_pkts); +} + +static void mxl862xx_get_rmon_stats(struct dsa_switch *ds, int port, + struct ethtool_rmon_stats *rmon_stats, + const struct ethtool_rmon_hist_range **ranges) +{ + struct mxl862xx_rmon_port_cnt cnt; + + if (mxl862xx_read_rmon(ds, port, &cnt)) + return; + + rmon_stats->undersize_pkts =3D le32_to_cpu(cnt.rx_under_size_good_pkts); + rmon_stats->oversize_pkts =3D le32_to_cpu(cnt.rx_oversize_good_pkts); + rmon_stats->fragments =3D le32_to_cpu(cnt.rx_under_size_error_pkts); + rmon_stats->jabbers =3D le32_to_cpu(cnt.rx_oversize_error_pkts); + + rmon_stats->hist[0] =3D le32_to_cpu(cnt.rx64byte_pkts); + rmon_stats->hist[1] =3D le32_to_cpu(cnt.rx127byte_pkts); + rmon_stats->hist[2] =3D le32_to_cpu(cnt.rx255byte_pkts); + rmon_stats->hist[3] =3D le32_to_cpu(cnt.rx511byte_pkts); + rmon_stats->hist[4] =3D le32_to_cpu(cnt.rx1023byte_pkts); + rmon_stats->hist[5] =3D le32_to_cpu(cnt.rx_max_byte_pkts); + + rmon_stats->hist_tx[0] =3D le32_to_cpu(cnt.tx64byte_pkts); + rmon_stats->hist_tx[1] =3D le32_to_cpu(cnt.tx127byte_pkts); + rmon_stats->hist_tx[2] =3D le32_to_cpu(cnt.tx255byte_pkts); + rmon_stats->hist_tx[3] =3D le32_to_cpu(cnt.tx511byte_pkts); + rmon_stats->hist_tx[4] =3D le32_to_cpu(cnt.tx1023byte_pkts); + rmon_stats->hist_tx[5] =3D le32_to_cpu(cnt.tx_max_byte_pkts); + + *ranges =3D mxl862xx_rmon_ranges; +} static const struct dsa_switch_ops mxl862xx_switch_ops =3D { .get_tag_protocol =3D mxl862xx_get_tag_protocol, .setup =3D mxl862xx_setup, @@ -1758,6 +1924,13 @@ static const struct dsa_switch_ops mxl862xx_switch_o= ps =3D { .port_vlan_filtering =3D mxl862xx_port_vlan_filtering, .port_vlan_add =3D mxl862xx_port_vlan_add, .port_vlan_del =3D mxl862xx_port_vlan_del, + .get_strings =3D mxl862xx_get_strings, + .get_sset_count =3D mxl862xx_get_sset_count, + .get_ethtool_stats =3D mxl862xx_get_ethtool_stats, + .get_eth_mac_stats =3D mxl862xx_get_eth_mac_stats, + .get_eth_ctrl_stats =3D mxl862xx_get_eth_ctrl_stats, + .get_pause_stats =3D mxl862xx_get_pause_stats, + .get_rmon_stats =3D mxl862xx_get_rmon_stats, }; =20 static void mxl862xx_phylink_mac_config(struct phylink_config *config, --=20 2.53.0 From nobody Mon Jun 15 15:20:45 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D80444A21; Sun, 12 Apr 2026 00:02:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775952134; cv=none; b=HA7s0P6CEBuMXKATrmBk//23n6+bpLqL9divwd3oU7UUaGCCL8LM2GKfQkff+gjnbZXmw4eJQxun/nZNvyw11keoVQeE9kvYEorUZu103nvrERVbcehkOqLzM4dUeiXeH3GrDhMcINUF3HsX/JrOKVqiSvSNo4luh4G0mZEnX1E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775952134; c=relaxed/simple; bh=WcZybTcTOTf8Oxk5rSUA/G9TjV9EyahRmeC3uVqs5FQ=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=pW7fVYrPi60GsxBuwi/uxq5J8+UwqSRpwwmEJ8KL5tfQe/2z37C0X3CE40/V9kCQ2ZMoHqkIo6/dkwR3s1cFjTF+FIHYc5HNm+8NhyEjVCf6juBUNxIoSEXLS2xK+0G8gOetoHi/+ya9AzmiSImJKCnzeUZKwIrrdKtspXMwdyM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1wBiHA-000000005aj-1OmI; Sun, 12 Apr 2026 00:02:08 +0000 Date: Sun, 12 Apr 2026 01:02:05 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v2 2/2] net: dsa: mxl862xx: implement .get_stats64 Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- v2: no changes drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 +- drivers/net/dsa/mxl862xx/mxl862xx.c | 175 +++++++++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx.h | 94 +++++++++++- 3 files changed, 270 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl= 862xx/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(); =20 - clear_bit(0, &priv->crc_err); + clear_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags); } =20 /* Firmware CRC error codes (outside normal Zephyr errno range). */ @@ -247,7 +247,7 @@ static int mxl862xx_issue_cmd(struct mxl862xx_priv *pri= v, u16 cmd, u16 len) =20 ret =3D 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 =3D=3D MXL862XX_FW_CRC6_ERR || ret =3D=3D 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 c= md, void *_data, } =20 if (crc16(0xffff, (const u8 *)data, size) !=3D 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 =3D -EIO; goto out; diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx= /mxl862xx.c index 58bf7210c6d40..b60482d93a855 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) =20 +/* 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; @@ -677,6 +683,9 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; =20 + schedule_delayed_work(&priv->stats_work, + MXL862XX_STATS_POLL_INTERVAL); + return mxl862xx_setup_mdio(ds); } =20 @@ -1900,6 +1909,159 @@ static void mxl862xx_get_rmon_stats(struct dsa_swit= ch *ds, int port, =20 *ranges =3D mxl862xx_rmon_ranges; } + +/* 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 sta= ts + * @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 =3D ds->priv; + struct mxl862xx_port_stats *s =3D &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 =3D le32_to_cpu(cnt.rx_good_pkts); + tx_pkts =3D le32_to_cpu(cnt.tx_good_pkts); + rx_bytes =3D le64_to_cpu(cnt.rx_good_bytes); + tx_bytes =3D le64_to_cpu(cnt.tx_good_bytes); + rx_fcserr =3D le32_to_cpu(cnt.rx_fcserror_pkts); + rx_under =3D le32_to_cpu(cnt.rx_under_size_error_pkts); + rx_over =3D le32_to_cpu(cnt.rx_oversize_error_pkts); + rx_align =3D le32_to_cpu(cnt.rx_align_error_pkts); + tx_drop =3D le32_to_cpu(cnt.tx_dropped_pkts); + rx_drop =3D le32_to_cpu(cnt.rx_dropped_pkts); + rx_evlan =3D le32_to_cpu(cnt.rx_extended_vlan_discard_pkts); + mtu_exc =3D le32_to_cpu(cnt.mtu_exceed_discard_pkts); + tx_acm =3D le32_to_cpu(cnt.tx_acm_dropped_pkts); + rx_mcast =3D le32_to_cpu(cnt.rx_multicast_pkts); + tx_coll =3D 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 +=3D mxl862xx_delta32(rx_pkts, s->prev_rx_good_pkts); + s->tx_packets +=3D mxl862xx_delta32(tx_pkts, s->prev_tx_good_pkts); + s->rx_bytes +=3D rx_bytes - s->prev_rx_good_bytes; + s->tx_bytes +=3D tx_bytes - s->prev_tx_good_bytes; + + s->rx_errors +=3D + 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 +=3D + mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts); + + s->rx_dropped +=3D + 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 +=3D + mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts) + + mxl862xx_delta32(tx_acm, s->prev_tx_acm_dropped_pkts); + + s->multicast +=3D mxl862xx_delta32(rx_mcast, s->prev_rx_multicast_pkts); + s->collisions +=3D mxl862xx_delta32(tx_coll, s->prev_tx_coll_count); + + s->rx_length_errors +=3D + 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 +=3D + mxl862xx_delta32(rx_fcserr, s->prev_rx_fcserror_pkts); + s->rx_frame_errors +=3D + mxl862xx_delta32(rx_align, s->prev_rx_align_error_pkts); + + s->prev_rx_good_pkts =3D rx_pkts; + s->prev_tx_good_pkts =3D tx_pkts; + s->prev_rx_good_bytes =3D rx_bytes; + s->prev_tx_good_bytes =3D tx_bytes; + s->prev_rx_fcserror_pkts =3D rx_fcserr; + s->prev_rx_under_size_error_pkts =3D rx_under; + s->prev_rx_oversize_error_pkts =3D rx_over; + s->prev_rx_align_error_pkts =3D rx_align; + s->prev_tx_dropped_pkts =3D tx_drop; + s->prev_rx_dropped_pkts =3D rx_drop; + s->prev_rx_evlan_discard_pkts =3D rx_evlan; + s->prev_mtu_exceed_discard_pkts =3D mtu_exc; + s->prev_tx_acm_dropped_pkts =3D tx_acm; + s->prev_rx_multicast_pkts =3D rx_mcast; + s->prev_tx_coll_count =3D tx_coll; + + spin_unlock_bh(&priv->ports[port].stats_lock); +} + +static void mxl862xx_stats_work_fn(struct work_struct *work) +{ + struct mxl862xx_priv *priv =3D + container_of(work, struct mxl862xx_priv, stats_work.work); + struct dsa_switch *ds =3D 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 =3D ds->priv; + struct mxl862xx_port_stats *ps =3D &priv->ports[port].stats; + + spin_lock_bh(&priv->ports[port].stats_lock); + + s->rx_packets =3D ps->rx_packets; + s->tx_packets =3D ps->tx_packets; + s->rx_bytes =3D ps->rx_bytes; + s->tx_bytes =3D ps->tx_bytes; + s->rx_errors =3D ps->rx_errors; + s->tx_errors =3D ps->tx_errors; + s->rx_dropped =3D ps->rx_dropped; + s->tx_dropped =3D ps->tx_dropped; + s->multicast =3D ps->multicast; + s->collisions =3D ps->collisions; + s->rx_length_errors =3D ps->rx_length_errors; + s->rx_crc_errors =3D ps->rx_crc_errors; + s->rx_frame_errors =3D 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 =3D { .get_tag_protocol =3D mxl862xx_get_tag_protocol, .setup =3D mxl862xx_setup, @@ -1931,6 +2093,7 @@ static const struct dsa_switch_ops mxl862xx_switch_op= s =3D { .get_eth_ctrl_stats =3D mxl862xx_get_eth_ctrl_stats, .get_pause_stats =3D mxl862xx_get_pause_stats, .get_rmon_stats =3D mxl862xx_get_rmon_stats, + .get_stats64 =3D mxl862xx_get_stats64, }; =20 static void mxl862xx_phylink_mac_config(struct phylink_config *config, @@ -1992,16 +2155,22 @@ static int mxl862xx_probe(struct mdio_device *mdiod= ev) priv->ports[i].priv =3D priv; INIT_WORK(&priv->ports[i].host_flood_work, mxl862xx_host_flood_work_fn); + spin_lock_init(&priv->ports[i].stats_lock); } =20 + INIT_DELAYED_WORK(&priv->stats_work, mxl862xx_stats_work_fn); + dev_set_drvdata(dev, ds); =20 err =3D 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 =3D 0; i < MXL862XX_MAX_PORTS; i++) cancel_work_sync(&priv->ports[i].host_flood_work); } + return err; } =20 @@ -2016,6 +2185,9 @@ static void mxl862xx_remove(struct mdio_device *mdiod= ev) =20 priv =3D ds->priv; =20 + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + cancel_delayed_work_sync(&priv->stats_work); + dsa_unregister_switch(ds); =20 mxl862xx_host_shutdown(priv); @@ -2042,6 +2214,9 @@ static void mxl862xx_shutdown(struct mdio_device *mdi= odev) =20 dsa_switch_shutdown(ds); =20 + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + cancel_delayed_work_sync(&priv->stats_work); + mxl862xx_host_shutdown(priv); =20 for (i =3D 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; }; =20 +/** + * 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 */ }; =20 +/* 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 fi= rmware * @crc_err_work: deferred work for shutting down all ports on MDIO = CRC * errors - * @crc_err: set atomically before CRC-triggered shutdown, clea= red - * 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 cancelli= ng + * 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 counte= rs + * 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; }; =20 #endif /* __MXL862XX_H */ --=20 2.53.0