* [PATCH net-next v3 1/3] net: hsr: Add standard LRE stats via RTM_GETSTATS / IFLA_STATS_LINK_XSTATS
2026-06-08 10:09 [PATCH net-next v3 0/3] Add standard stats for HSR/PRP MD Danish Anwar
@ 2026-06-08 10:09 ` MD Danish Anwar
2026-06-08 10:09 ` [PATCH net-next v3 2/3] net: ti: icssg: Add static_assert to guard stat array counts MD Danish Anwar
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: MD Danish Anwar @ 2026-06-08 10:09 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, MD Danish Anwar,
Roger Quadros, Andrew Lunn, Jacob Keller, Meghana Malladi,
David Carlier, Vadim Fedorenko, Kevin Hao, Himanshu Mittal,
Hangbin Liu, Markus Elfring, Fernando Fernandez Mancera,
Jan Vaclav
Cc: netdev, linux-doc, linux-kernel, linux-arm-kernel
Per the IEC-62439-3 specification the Link Redundancy Entity (LRE)
maintains a well-defined set of counters applicable to both software
and offloaded HSR/PRP implementations. Define these counters as
individual netlink attributes inside a LINK_XSTATS_TYPE_HSR nest,
following the approach used by bridge and bond with IFLA_STATS_LINK_XSTATS.
The full IEC-62439-3 MIB counter set is represented, with per-port (A,
B, C) granularity where applicable:
lreCntTx{A,B,C} - sent HSR/PRP tagged frames per port
lreCntRx{A,B,C} - received HSR/PRP tagged frames per port
lreCntErrWrongLan{A,B,C} - received frames with wrong LAN ID (PRP)
lreCntErrors{A,B,C} - received frames with errors per port
lreCntUnique{A,B,C} - frames received without duplicate
lreCntDuplicate{A,B,C} - frames received with exactly one duplicate
lreCntMulti{A,B,C} - frames received with more than one duplicate
lreCntOwnRx{A,B} - own-address frames received (HSR only)
Each counter is encoded as its own HSR_XSTATS_* u64 netlink attribute.
Unsupported counters are initialised to ~0ULL by the kernel and omitted
from the netlink reply; user-space must treat an absent attribute as
"not available".
The UAPI attribute enum (HSR_XSTATS_*) is added to hsr_netlink.h.
LINK_XSTATS_TYPE_HSR is added to the LINK_XSTATS_TYPE_* enum in both
include/uapi/linux/if_link.h and tools/include/uapi/linux/if_link.h.
A kernel-internal struct hsr_lre_stats (in linux/if_hsr.h) is provided
for offload drivers to fill via ndo_get_offload_stats. Unsupported
fields must be left at the ~0ULL value initialised by the HSR layer
before calling the NDO.
The HSR stack calls ndo_get_offload_stats(IFLA_STATS_LINK_XSTATS) on
slave A to collect offload counters.
Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
---
include/linux/if_hsr.h | 48 +++++++++++
include/uapi/linux/hsr_netlink.h | 56 ++++++++++++
include/uapi/linux/if_link.h | 1 +
net/hsr/hsr_netlink.c | 132 +++++++++++++++++++++++++++--
tools/include/uapi/linux/if_link.h | 1 +
5 files changed, 230 insertions(+), 8 deletions(-)
diff --git a/include/linux/if_hsr.h b/include/linux/if_hsr.h
index f4cf2dd36d193..b8c20f0906194 100644
--- a/include/linux/if_hsr.h
+++ b/include/linux/if_hsr.h
@@ -38,6 +38,54 @@ struct hsr_tag {
#define HSR_HLEN 6
+/**
+ * struct hsr_lre_stats - Kernel-internal IEC-62439-3 LRE counter set.
+ *
+ * This is the buffer type written by ndo_get_offload_stats() when called
+ * with attr_id == IFLA_STATS_LINK_XSTATS on an HSR slave device. Each
+ * field maps to one HSR_XSTATS_* netlink attribute. Fields that the
+ * offload driver does not support must be left at the initialised value of
+ * ~0ULL; the HSR layer will skip those when building the netlink reply.
+ *
+ * Per-port suffix: _a = port A (slave 1 / LAN-A),
+ * _b = port B (slave 2 / LAN-B),
+ * _c = interlink / application interface.
+ *
+ * @cnt_tx_a: lreCntTxA - sent HSR/PRP tagged frames on port A.
+ * @cnt_tx_b: lreCntTxB - sent HSR/PRP tagged frames on port B.
+ * @cnt_tx_c: lreCntTxC - sent HSR/PRP tagged frames on port C.
+ * @cnt_rx_a: lreCntRxA - received HSR/PRP tagged frames on port A.
+ * @cnt_rx_b: lreCntRxB - received HSR/PRP tagged frames on port B.
+ * @cnt_rx_c: lreCntRxC - received HSR/PRP tagged frames on port C.
+ * @cnt_err_wrong_lan_a: lreCntErrWrongLanA - wrong LAN ID frames on port A.
+ * @cnt_err_wrong_lan_b: lreCntErrWrongLanB - wrong LAN ID frames on port B.
+ * @cnt_err_wrong_lan_c: lreCntErrWrongLanC - wrong LAN ID frames on port C.
+ * @cnt_errors_a: lreCntErrorsA - received frames with errors on port A.
+ * @cnt_errors_b: lreCntErrorsB - received frames with errors on port B.
+ * @cnt_errors_c: lreCntErrorsC - received frames with errors on port C.
+ * @cnt_unique_a: lreCntUniqueA - frames received without duplicate on port A.
+ * @cnt_unique_b: lreCntUniqueB - frames received without duplicate on port B.
+ * @cnt_unique_c: lreCntUniqueC - frames received without duplicate on port C.
+ * @cnt_duplicate_a: lreCntDuplicateA - frames with one duplicate on port A.
+ * @cnt_duplicate_b: lreCntDuplicateB - frames with one duplicate on port B.
+ * @cnt_duplicate_c: lreCntDuplicateC - frames with one duplicate on port C.
+ * @cnt_multi_a: lreCntMultiA - frames with more than one duplicate on port A.
+ * @cnt_multi_b: lreCntMultiB - frames with more than one duplicate on port B.
+ * @cnt_multi_c: lreCntMultiC - frames with more than one duplicate on port C.
+ * @cnt_own_rx_a: lreCntOwnRxA - own-address frames received on port A.
+ * @cnt_own_rx_b: lreCntOwnRxB - own-address frames received on port B.
+ */
+struct hsr_lre_stats {
+ u64 cnt_tx_a, cnt_tx_b, cnt_tx_c;
+ u64 cnt_rx_a, cnt_rx_b, cnt_rx_c;
+ u64 cnt_err_wrong_lan_a, cnt_err_wrong_lan_b, cnt_err_wrong_lan_c;
+ u64 cnt_errors_a, cnt_errors_b, cnt_errors_c;
+ u64 cnt_unique_a, cnt_unique_b, cnt_unique_c;
+ u64 cnt_duplicate_a, cnt_duplicate_b, cnt_duplicate_c;
+ u64 cnt_multi_a, cnt_multi_b, cnt_multi_c;
+ u64 cnt_own_rx_a, cnt_own_rx_b;
+};
+
#if IS_ENABLED(CONFIG_HSR)
extern bool is_hsr_master(struct net_device *dev);
extern int hsr_get_version(struct net_device *dev, enum hsr_version *ver);
diff --git a/include/uapi/linux/hsr_netlink.h b/include/uapi/linux/hsr_netlink.h
index d540ea9bbef4b..c414a2bb93b79 100644
--- a/include/uapi/linux/hsr_netlink.h
+++ b/include/uapi/linux/hsr_netlink.h
@@ -48,4 +48,60 @@ enum {
};
#define HSR_C_MAX (__HSR_C_MAX - 1)
+/* HSR/PRP LRE extended statistics attributes.
+ * Reported inside LINK_XSTATS_TYPE_HSR (RTM_GETSTATS / ip stats show).
+ * Counter definitions follow IEC-62439-3 MIB naming.
+ *
+ * All counters are __u64. Unsupported counters are omitted from the
+ * netlink reply; user-space must treat an absent attribute as "not available".
+ *
+ * Per-port suffix: _A = port A (slave 1), _B = port B (slave 2),
+ * _C = interlink / application interface.
+ */
+enum {
+ /* Sent HSR/PRP tagged frames per port */
+ HSR_XSTATS_CNT_TX_A = 1,
+ HSR_XSTATS_CNT_TX_B,
+ HSR_XSTATS_CNT_TX_C,
+
+ /* Received HSR/PRP tagged frames per port */
+ HSR_XSTATS_CNT_RX_A,
+ HSR_XSTATS_CNT_RX_B,
+ HSR_XSTATS_CNT_RX_C,
+
+ /* Received frames with wrong LAN ID (PRP only) per port */
+ HSR_XSTATS_CNT_ERR_WRONG_LAN_A,
+ HSR_XSTATS_CNT_ERR_WRONG_LAN_B,
+ HSR_XSTATS_CNT_ERR_WRONG_LAN_C,
+
+ /* Received frames with errors per port */
+ HSR_XSTATS_CNT_ERRORS_A,
+ HSR_XSTATS_CNT_ERRORS_B,
+ HSR_XSTATS_CNT_ERRORS_C,
+
+ /* Frames received with no duplicate per port */
+ HSR_XSTATS_CNT_UNIQUE_A,
+ HSR_XSTATS_CNT_UNIQUE_B,
+ HSR_XSTATS_CNT_UNIQUE_C,
+
+ /* Frames received with exactly one duplicate per port */
+ HSR_XSTATS_CNT_DUPLICATE_A,
+ HSR_XSTATS_CNT_DUPLICATE_B,
+ HSR_XSTATS_CNT_DUPLICATE_C,
+
+ /* Frames received with more than one duplicate per port */
+ HSR_XSTATS_CNT_MULTI_A,
+ HSR_XSTATS_CNT_MULTI_B,
+ HSR_XSTATS_CNT_MULTI_C,
+
+ /* Frames received matching this node's own address (HSR only) */
+ HSR_XSTATS_CNT_OWN_RX_A,
+ HSR_XSTATS_CNT_OWN_RX_B,
+
+ HSR_XSTATS_PAD,
+ __HSR_XSTATS_MAX,
+};
+
+#define HSR_XSTATS_MAX (__HSR_XSTATS_MAX - 1)
+
#endif /* __UAPI_HSR_NETLINK_H */
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 79ce4bc24cba6..3dcd51e64f29d 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -1905,6 +1905,7 @@ enum {
LINK_XSTATS_TYPE_UNSPEC,
LINK_XSTATS_TYPE_BRIDGE,
LINK_XSTATS_TYPE_BOND,
+ LINK_XSTATS_TYPE_HSR,
__LINK_XSTATS_TYPE_MAX
};
#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index db0b0af7a6920..9455f65868ca2 100644
--- a/net/hsr/hsr_netlink.c
+++ b/net/hsr/hsr_netlink.c
@@ -11,6 +11,8 @@
#include <linux/kernel.h>
#include <net/rtnetlink.h>
#include <net/genetlink.h>
+#include <uapi/linux/if_link.h>
+#include <uapi/linux/hsr_netlink.h>
#include "hsr_main.h"
#include "hsr_device.h"
#include "hsr_framereg.h"
@@ -189,15 +191,129 @@ static int hsr_fill_info(struct sk_buff *skb, const struct net_device *dev)
return -EMSGSIZE;
}
+/*
+ * Number of real HSR_XSTATS_* u64 counter attributes.
+ * Real counters run from HSR_XSTATS_CNT_TX_A(1) through
+ * HSR_XSTATS_CNT_OWN_RX_B(25); HSR_XSTATS_PAD is not a counter.
+ */
+#define HSR_XSTATS_CNT_ATTRS (HSR_XSTATS_PAD - 1)
+
+static size_t hsr_get_linkxstats_size(const struct net_device *dev, int attr)
+{
+ if (attr != IFLA_STATS_LINK_XSTATS)
+ return 0;
+
+ /* Nest header (LINK_XSTATS_TYPE_HSR) + one u64 nla per counter */
+ return nla_total_size(0) +
+ HSR_XSTATS_CNT_ATTRS * nla_total_size_64bit(sizeof(u64));
+}
+
+/* Put a u64 counter attribute; skip if value is ~0ULL (unsupported). */
+static int hsr_put_stat(struct sk_buff *skb, int attr_id, u64 val)
+{
+ if (val == ~0ULL)
+ return 0;
+ return nla_put_u64_64bit(skb, attr_id, val, HSR_XSTATS_PAD);
+}
+
+static int hsr_fill_linkxstats(struct sk_buff *skb,
+ const struct net_device *dev,
+ int *prividx, int attr)
+{
+ struct hsr_lre_stats stats;
+ struct hsr_port *port;
+ struct hsr_priv *hsr = netdev_priv(dev);
+ struct nlattr *nest;
+ int s_prividx = *prividx;
+ int err;
+
+ if (attr != IFLA_STATS_LINK_XSTATS)
+ return 0;
+
+ *prividx = 0;
+
+ nest = nla_nest_start_noflag(skb, LINK_XSTATS_TYPE_HSR);
+ if (!nest)
+ return -EMSGSIZE;
+
+ /* Initialise all counters to ~0ULL ("unsupported") */
+ memset(&stats, 0xff, sizeof(stats));
+
+ /* Ask the offload driver (if any) via ndo_get_offload_stats on slave A.
+ * Guard with ndo_has_offload_stats so we only call drivers that
+ * explicitly declare support for IFLA_STATS_LINK_XSTATS, avoiding
+ * spurious -EINVAL from drivers that implement the NDO for a different
+ * attr_id (e.g. IFLA_OFFLOAD_XSTATS_CPU_HIT).
+ */
+ port = hsr_port_get_hsr(hsr, HSR_PT_SLAVE_A);
+ if (port) {
+ const struct net_device_ops *ops = port->dev->netdev_ops;
+
+ if (ops->ndo_has_offload_stats &&
+ ops->ndo_has_offload_stats(port->dev,
+ IFLA_STATS_LINK_XSTATS) &&
+ ops->ndo_get_offload_stats) {
+ err = ops->ndo_get_offload_stats(IFLA_STATS_LINK_XSTATS,
+ port->dev, &stats);
+ if (err && err != -EOPNOTSUPP) {
+ nla_nest_cancel(skb, nest);
+ return err;
+ }
+ }
+ }
+
+#define PUT_STAT(attr, field) \
+ do { \
+ if (HSR_XSTATS_##attr < s_prividx) \
+ break; \
+ if (hsr_put_stat(skb, HSR_XSTATS_##attr, stats.field)) { \
+ *prividx = HSR_XSTATS_##attr; \
+ nla_nest_end(skb, nest); \
+ return -EMSGSIZE; \
+ } \
+ } while (0)
+
+ PUT_STAT(CNT_TX_A, cnt_tx_a);
+ PUT_STAT(CNT_TX_B, cnt_tx_b);
+ PUT_STAT(CNT_TX_C, cnt_tx_c);
+ PUT_STAT(CNT_RX_A, cnt_rx_a);
+ PUT_STAT(CNT_RX_B, cnt_rx_b);
+ PUT_STAT(CNT_RX_C, cnt_rx_c);
+ PUT_STAT(CNT_ERR_WRONG_LAN_A, cnt_err_wrong_lan_a);
+ PUT_STAT(CNT_ERR_WRONG_LAN_B, cnt_err_wrong_lan_b);
+ PUT_STAT(CNT_ERR_WRONG_LAN_C, cnt_err_wrong_lan_c);
+ PUT_STAT(CNT_ERRORS_A, cnt_errors_a);
+ PUT_STAT(CNT_ERRORS_B, cnt_errors_b);
+ PUT_STAT(CNT_ERRORS_C, cnt_errors_c);
+ PUT_STAT(CNT_UNIQUE_A, cnt_unique_a);
+ PUT_STAT(CNT_UNIQUE_B, cnt_unique_b);
+ PUT_STAT(CNT_UNIQUE_C, cnt_unique_c);
+ PUT_STAT(CNT_DUPLICATE_A, cnt_duplicate_a);
+ PUT_STAT(CNT_DUPLICATE_B, cnt_duplicate_b);
+ PUT_STAT(CNT_DUPLICATE_C, cnt_duplicate_c);
+ PUT_STAT(CNT_MULTI_A, cnt_multi_a);
+ PUT_STAT(CNT_MULTI_B, cnt_multi_b);
+ PUT_STAT(CNT_MULTI_C, cnt_multi_c);
+ PUT_STAT(CNT_OWN_RX_A, cnt_own_rx_a);
+ PUT_STAT(CNT_OWN_RX_B, cnt_own_rx_b);
+
+#undef PUT_STAT
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
static struct rtnl_link_ops hsr_link_ops __read_mostly = {
- .kind = "hsr",
- .maxtype = IFLA_HSR_MAX,
- .policy = hsr_policy,
- .priv_size = sizeof(struct hsr_priv),
- .setup = hsr_dev_setup,
- .newlink = hsr_newlink,
- .dellink = hsr_dellink,
- .fill_info = hsr_fill_info,
+ .kind = "hsr",
+ .maxtype = IFLA_HSR_MAX,
+ .policy = hsr_policy,
+ .priv_size = sizeof(struct hsr_priv),
+ .setup = hsr_dev_setup,
+ .newlink = hsr_newlink,
+ .dellink = hsr_dellink,
+ .fill_info = hsr_fill_info,
+ .get_linkxstats_size = hsr_get_linkxstats_size,
+ .fill_linkxstats = hsr_fill_linkxstats,
};
/* attribute policy */
diff --git a/tools/include/uapi/linux/if_link.h b/tools/include/uapi/linux/if_link.h
index 7e46ca4cd31bb..13f122996d01a 100644
--- a/tools/include/uapi/linux/if_link.h
+++ b/tools/include/uapi/linux/if_link.h
@@ -1844,6 +1844,7 @@ enum {
LINK_XSTATS_TYPE_UNSPEC,
LINK_XSTATS_TYPE_BRIDGE,
LINK_XSTATS_TYPE_BOND,
+ LINK_XSTATS_TYPE_HSR,
__LINK_XSTATS_TYPE_MAX
};
#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net-next v3 3/3] net: ti: icssg: Add HSR offload statistics support
2026-06-08 10:09 [PATCH net-next v3 0/3] Add standard stats for HSR/PRP MD Danish Anwar
2026-06-08 10:09 ` [PATCH net-next v3 1/3] net: hsr: Add standard LRE stats via RTM_GETSTATS / IFLA_STATS_LINK_XSTATS MD Danish Anwar
2026-06-08 10:09 ` [PATCH net-next v3 2/3] net: ti: icssg: Add static_assert to guard stat array counts MD Danish Anwar
@ 2026-06-08 10:09 ` MD Danish Anwar
2026-06-10 18:47 ` [PATCH net-next v3 0/3] Add standard stats for HSR/PRP Simon Horman
3 siblings, 0 replies; 5+ messages in thread
From: MD Danish Anwar @ 2026-06-08 10:09 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, MD Danish Anwar,
Roger Quadros, Andrew Lunn, Jacob Keller, Meghana Malladi,
David Carlier, Vadim Fedorenko, Kevin Hao, Himanshu Mittal,
Hangbin Liu, Markus Elfring, Fernando Fernandez Mancera,
Jan Vaclav
Cc: netdev, linux-doc, linux-kernel, linux-arm-kernel
Add support for exposing ICSSG HSR statistics through two interfaces:
ethtool and the standard RTM_GETSTATS / IFLA_STATS_LINK_XSTATS path.
Add a standard_stats flag to struct icssg_pa_stats and extend
icssg_all_pa_stats[] with 10 new entries:
Firmware-specific HSR counters (standard_stats=false, ethtool only):
- FW_HSR_FWD_CHECK_FAIL_DROP
- FW_HSR_HE_CHECK_FAIL_DROP
- FW_HSR_SKIP_HOST_DUP_DISCARD
IEC 62439-3 LRE counters (standard_stats=true, excluded from ethtool):
- FW_LRE_CNT_UNIQUE_RX, FW_LRE_CNT_DUPLICATE_RX, FW_LRE_CNT_MULTIPLE_RX
- FW_LRE_CNT_RX, FW_LRE_CNT_TX, FW_LRE_CNT_OWN_RX
- FW_LRE_CNT_ERRWRONGLAN
The ethtool get_strings/get_ethtool_stats callbacks skip entries with
standard_stats=true so they do not appear as ethtool counters.
ICSSG_NUM_PA_STANDARD_STATS is introduced and accounted for in
ICSSG_NUM_ETHTOOL_STATS so the sset count stays accurate.
ICSSG_NUM_PA_STATS is updated from 32 to 42.
Implement ndo_has_offload_stats() and ndo_get_offload_stats() in
emac_netdev_ops to expose the IEC 62439-3 LRE counters via the HSR
stack's RTM_GETSTATS / IFLA_STATS_LINK_XSTATS interface. The HSR stack
calls these NDOs on slave A; the callback reads PA stat registers for
both ports (MAC0 = port A, MAC1 = port B) from the shared prueth
instance and fills struct hsr_lre_stats. Port C counters are not
available in ICSSG hardware and remain at ~0ULL.
Export emac_update_hardware_stats() and emac_get_stat_by_name() as
GPL symbols so they can be called from icssg_prueth.c. Also change
emac_get_stat_by_name() return type from int to u64 and make it return
~0ULL on an unknown stat name instead of -EINVAL, consistent with the
hsr_lre_stats sentinel convention.
Add FW_HSR_FWD_CHECK_FAIL_DROP and FW_HSR_HE_CHECK_FAIL_DROP to the
rx_dropped sum in ndo_get_stats64, as these represent frames discarded
by the HSR forwarding logic.
Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
---
.../ethernet/ti/icssg_prueth.rst | 19 ++++
drivers/net/ethernet/ti/icssg/icssg_common.c | 7 +-
drivers/net/ethernet/ti/icssg/icssg_ethtool.c | 10 +-
drivers/net/ethernet/ti/icssg/icssg_prueth.c | 91 +++++++++++++++++++
drivers/net/ethernet/ti/icssg/icssg_prueth.h | 10 +-
drivers/net/ethernet/ti/icssg/icssg_stats.c | 6 +-
drivers/net/ethernet/ti/icssg/icssg_stats.h | 85 +++++++++--------
.../net/ethernet/ti/icssg/icssg_switch_map.h | 10 ++
8 files changed, 192 insertions(+), 46 deletions(-)
diff --git a/Documentation/networking/device_drivers/ethernet/ti/icssg_prueth.rst b/Documentation/networking/device_drivers/ethernet/ti/icssg_prueth.rst
index da21ddf431bbc..faa1fc18a6737 100644
--- a/Documentation/networking/device_drivers/ethernet/ti/icssg_prueth.rst
+++ b/Documentation/networking/device_drivers/ethernet/ti/icssg_prueth.rst
@@ -54,3 +54,22 @@ These statistics are as follows,
- ``FW_HOST_TX_PKT_CNT``: Number of valid packets copied by RTU0 to Tx queues
- ``FW_HOST_EGRESS_Q_PRE_OVERFLOW``: Host Egress Q (Pre-emptible) Overflow Counter
- ``FW_HOST_EGRESS_Q_EXP_OVERFLOW``: Host Egress Q (Pre-emptible) Overflow Counter
+ - ``FW_HSR_FWD_CHECK_FAIL_DROP``: Packets dropped on the HSR forwarding path due to failed checks
+ - ``FW_HSR_HE_CHECK_FAIL_DROP``: Packets dropped on the host egress path due to failed checks
+ - ``FW_HSR_SKIP_HOST_DUP_DISCARD``: Frames for which the host duplicate discard check was skipped
+
+HSR/LRE Standard Statistics
+============================
+
+When the ICSSG operates in HSR offload mode the driver exposes the IEC 62439-3
+LRE counters through the standard netlink stats interface.
+
+The following per-port (port A and port B) LRE counters are reported:
+
+ - ``lreCntTx``: Number of HSR/PRP tagged frames sent
+ - ``lreCntRx``: Number of HSR/PRP tagged frames received
+ - ``lreCntUnique``: Number of frames received with no duplicate detected
+ - ``lreCntDuplicate``: Number of frames received for which exactly one duplicate was detected
+ - ``lreCntMultiple``: Number of frames received for which more than one duplicate was detected
+ - ``lreCntOwnRx``: Number of HSR/PRP tagged frames received whose source MAC matches the node's own address
+ - ``lreCntErrWrongLan``: Number of frames received with a wrong LAN identifier (PRP only)
diff --git a/drivers/net/ethernet/ti/icssg/icssg_common.c b/drivers/net/ethernet/ti/icssg/icssg_common.c
index a28a608f9bf4b..1fcb031949535 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_common.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_common.c
@@ -1643,7 +1643,12 @@ void icssg_ndo_get_stats64(struct net_device *ndev,
emac_get_stat_by_name(emac, "FW_INF_DROP_TAGGED") +
emac_get_stat_by_name(emac, "FW_INF_DROP_PRIOTAGGED") +
emac_get_stat_by_name(emac, "FW_INF_DROP_NOTAG") +
- emac_get_stat_by_name(emac, "FW_INF_DROP_NOTMEMBER");
+ emac_get_stat_by_name(emac,
+ "FW_INF_DROP_NOTMEMBER") +
+ emac_get_stat_by_name(emac,
+ "FW_HSR_FWD_CHECK_FAIL_DROP") +
+ emac_get_stat_by_name(emac,
+ "FW_HSR_HE_CHECK_FAIL_DROP");
stats->tx_errors = ndev->stats.tx_errors;
stats->tx_dropped = ndev->stats.tx_dropped +
emac_get_stat_by_name(emac, "FW_RTU_PKT_DROP") +
diff --git a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
index b715af21d23ac..7a99c99aab1e8 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
@@ -74,7 +74,9 @@ static int emac_get_sset_count(struct net_device *ndev, int stringset)
if (emac->prueth->pa_stats)
return ICSSG_NUM_ETHTOOL_STATS;
else
- return ICSSG_NUM_ETHTOOL_STATS - ICSSG_NUM_PA_STATS;
+ return ICSSG_NUM_ETHTOOL_STATS -
+ (ICSSG_NUM_PA_STATS -
+ ICSSG_NUM_PA_STANDARD_STATS);
default:
return -EOPNOTSUPP;
}
@@ -93,7 +95,8 @@ static void emac_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
ethtool_puts(&p, icssg_all_miig_stats[i].name);
if (emac->prueth->pa_stats)
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++)
- ethtool_puts(&p, icssg_all_pa_stats[i].name);
+ if (!icssg_all_pa_stats[i].standard_stats)
+ ethtool_puts(&p, icssg_all_pa_stats[i].name);
break;
default:
break;
@@ -114,7 +117,8 @@ static void emac_get_ethtool_stats(struct net_device *ndev,
if (emac->prueth->pa_stats)
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++)
- *(data++) = emac->pa_stats[i];
+ if (!icssg_all_pa_stats[i].standard_stats)
+ *(data++) = emac->pa_stats[i];
}
static int emac_get_ts_info(struct net_device *ndev,
diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.c b/drivers/net/ethernet/ti/icssg/icssg_prueth.c
index 591be5c8056b4..bd390ccf7e450 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_prueth.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.c
@@ -14,6 +14,7 @@
#include <linux/etherdevice.h>
#include <linux/genalloc.h>
#include <linux/if_hsr.h>
+#include <linux/if_link.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-hi-lo.h>
@@ -1633,6 +1634,94 @@ int prueth_xsk_wakeup(struct net_device *ndev, u32 qid, u32 flags)
return 0;
}
+/**
+ * prueth_ndo_get_offload_stats - Fill standard LRE counters from ICSSG.
+ * @attr_id: Stats attribute ID; only IFLA_STATS_LINK_XSTATS is handled.
+ * @dev: Slave net_device (port A) whose offload stats are requested.
+ * @sp: Output pointer; cast to struct hsr_lre_stats *.
+ *
+ * Called by the HSR stack via ndo_get_offload_stats on the slave A device.
+ * Fetches the per-port PA stat register snapshots for port A and port B,
+ * and fills the IEC-62439-3 per-port LRE counters. Port C (interlink)
+ * counters are not available in ICSSG hardware and remain at ~0ULL.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not support
+ * HSR offload statistics for the requested attribute.
+ */
+static int prueth_ndo_get_offload_stats(int attr_id,
+ const struct net_device *dev,
+ void *sp)
+{
+ struct hsr_lre_stats *stats = sp;
+ struct prueth_emac *emac0;
+ struct prueth_emac *emac1;
+ struct prueth_emac *emac = netdev_priv(dev);
+ struct prueth *prueth = emac->prueth;
+
+ if (attr_id != IFLA_STATS_LINK_XSTATS)
+ return -EOPNOTSUPP;
+
+ if (!prueth->is_hsr_offload_mode)
+ return -EOPNOTSUPP;
+
+ emac0 = prueth->emac[PRUETH_MAC0];
+ emac1 = prueth->emac[PRUETH_MAC1];
+
+ if (!prueth->pa_stats)
+ return -EOPNOTSUPP;
+
+ /* Initialise all fields to ~0ULL ("unsupported"); only port A and B
+ * counters are filled — port C and aggregate counters are not
+ * available in ICSSG hardware.
+ */
+ memset(stats, 0xff, sizeof(*stats));
+
+ emac_update_hardware_stats(emac0);
+ stats->cnt_tx_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_TX");
+ stats->cnt_rx_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_RX");
+ stats->cnt_unique_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_UNIQUE_RX");
+ stats->cnt_duplicate_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_DUPLICATE_RX");
+ stats->cnt_multi_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_MULTIPLE_RX");
+ stats->cnt_own_rx_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_OWN_RX");
+ /* lreCntErrWrongLan is PRP only */
+ stats->cnt_err_wrong_lan_a =
+ emac_get_stat_by_name(emac0, "FW_LRE_CNT_ERRWRONGLAN");
+
+ emac_update_hardware_stats(emac1);
+ stats->cnt_tx_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_TX");
+ stats->cnt_rx_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_RX");
+ stats->cnt_unique_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_UNIQUE_RX");
+ stats->cnt_duplicate_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_DUPLICATE_RX");
+ stats->cnt_multi_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_MULTIPLE_RX");
+ stats->cnt_own_rx_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_OWN_RX");
+ stats->cnt_err_wrong_lan_b =
+ emac_get_stat_by_name(emac1, "FW_LRE_CNT_ERRWRONGLAN");
+
+ return 0;
+}
+
+static bool prueth_ndo_has_offload_stats(const struct net_device *dev,
+ int attr_id)
+{
+ struct prueth_emac *emac = netdev_priv(dev);
+ struct prueth *prueth = emac->prueth;
+
+ return attr_id == IFLA_STATS_LINK_XSTATS &&
+ prueth->is_hsr_offload_mode && prueth->pa_stats;
+}
+
static const struct net_device_ops emac_netdev_ops = {
.ndo_open = emac_ndo_open,
.ndo_stop = emac_ndo_stop,
@@ -1652,6 +1741,8 @@ static const struct net_device_ops emac_netdev_ops = {
.ndo_hwtstamp_get = icssg_ndo_get_ts_config,
.ndo_hwtstamp_set = icssg_ndo_set_ts_config,
.ndo_xsk_wakeup = prueth_xsk_wakeup,
+ .ndo_has_offload_stats = prueth_ndo_has_offload_stats,
+ .ndo_get_offload_stats = prueth_ndo_get_offload_stats,
};
static int prueth_netdev_init(struct prueth *prueth,
diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.h b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
index df93d15c5b786..d6c221e897924 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_prueth.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
@@ -57,12 +57,14 @@
#define ICSSG_MAX_RFLOWS 8 /* per slice */
-#define ICSSG_NUM_PA_STATS 32
+#define ICSSG_NUM_PA_STATS 42
#define ICSSG_NUM_MIIG_STATS 60
/* Number of ICSSG related stats */
#define ICSSG_NUM_STATS (ICSSG_NUM_MIIG_STATS + ICSSG_NUM_PA_STATS)
-#define ICSSG_NUM_STANDARD_STATS 31
-#define ICSSG_NUM_ETHTOOL_STATS (ICSSG_NUM_STATS - ICSSG_NUM_STANDARD_STATS)
+#define ICSSG_NUM_STANDARD_STATS 31
+#define ICSSG_NUM_PA_STANDARD_STATS 7
+#define ICSSG_NUM_ETHTOOL_STATS (ICSSG_NUM_STATS - ICSSG_NUM_STANDARD_STATS - \
+ ICSSG_NUM_PA_STANDARD_STATS)
#define IEP_DEFAULT_CYCLE_TIME_NS 1000000 /* 1 ms */
@@ -458,7 +460,7 @@ int emac_fdb_flow_id_updated(struct prueth_emac *emac);
void icssg_stats_work_handler(struct work_struct *work);
void emac_update_hardware_stats(struct prueth_emac *emac);
-int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name);
+u64 emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name);
/* Common functions */
void prueth_cleanup_rx_chns(struct prueth_emac *emac,
diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.c b/drivers/net/ethernet/ti/icssg/icssg_stats.c
index 7159baa0155cf..9950d0ba899fa 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_stats.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_stats.c
@@ -62,6 +62,7 @@ void emac_update_hardware_stats(struct prueth_emac *emac)
spin_unlock(&prueth->stats_lock);
}
+EXPORT_SYMBOL_GPL(emac_update_hardware_stats);
void icssg_stats_work_handler(struct work_struct *work)
{
@@ -74,7 +75,7 @@ void icssg_stats_work_handler(struct work_struct *work)
}
EXPORT_SYMBOL_GPL(icssg_stats_work_handler);
-int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
+u64 emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
{
int i;
@@ -91,5 +92,6 @@ int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
}
netdev_err(emac->ndev, "Invalid stats %s\n", stat_name);
- return -EINVAL;
+ return ~0ULL;
}
+EXPORT_SYMBOL_GPL(emac_get_stat_by_name);
diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.h b/drivers/net/ethernet/ti/icssg/icssg_stats.h
index 6f4400d8a0f61..373debfb815cc 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_stats.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_stats.h
@@ -157,50 +157,63 @@ static const struct icssg_miig_stats icssg_all_miig_stats[] = {
static_assert(ARRAY_SIZE(icssg_all_miig_stats) == ICSSG_NUM_MIIG_STATS);
-#define ICSSG_PA_STATS(field) \
-{ \
- #field, \
- field, \
+#define ICSSG_PA_STATS(field, stats_type) \
+{ \
+ #field, \
+ field, \
+ stats_type \
}
struct icssg_pa_stats {
char name[ETH_GSTRING_LEN];
u32 offset;
+ bool standard_stats;
};
static const struct icssg_pa_stats icssg_all_pa_stats[] = {
- ICSSG_PA_STATS(FW_RTU_PKT_DROP),
- ICSSG_PA_STATS(FW_Q0_OVERFLOW),
- ICSSG_PA_STATS(FW_Q1_OVERFLOW),
- ICSSG_PA_STATS(FW_Q2_OVERFLOW),
- ICSSG_PA_STATS(FW_Q3_OVERFLOW),
- ICSSG_PA_STATS(FW_Q4_OVERFLOW),
- ICSSG_PA_STATS(FW_Q5_OVERFLOW),
- ICSSG_PA_STATS(FW_Q6_OVERFLOW),
- ICSSG_PA_STATS(FW_Q7_OVERFLOW),
- ICSSG_PA_STATS(FW_DROPPED_PKT),
- ICSSG_PA_STATS(FW_RX_ERROR),
- ICSSG_PA_STATS(FW_RX_DS_INVALID),
- ICSSG_PA_STATS(FW_TX_DROPPED_PACKET),
- ICSSG_PA_STATS(FW_TX_TS_DROPPED_PACKET),
- ICSSG_PA_STATS(FW_INF_PORT_DISABLED),
- ICSSG_PA_STATS(FW_INF_SAV),
- ICSSG_PA_STATS(FW_INF_SA_DL),
- ICSSG_PA_STATS(FW_INF_PORT_BLOCKED),
- ICSSG_PA_STATS(FW_INF_DROP_TAGGED),
- ICSSG_PA_STATS(FW_INF_DROP_PRIOTAGGED),
- ICSSG_PA_STATS(FW_INF_DROP_NOTAG),
- ICSSG_PA_STATS(FW_INF_DROP_NOTMEMBER),
- ICSSG_PA_STATS(FW_RX_EOF_SHORT_FRMERR),
- ICSSG_PA_STATS(FW_RX_B0_DROP_EARLY_EOF),
- ICSSG_PA_STATS(FW_TX_JUMBO_FRM_CUTOFF),
- ICSSG_PA_STATS(FW_RX_EXP_FRAG_Q_DROP),
- ICSSG_PA_STATS(FW_RX_FIFO_OVERRUN),
- ICSSG_PA_STATS(FW_CUT_THR_PKT),
- ICSSG_PA_STATS(FW_HOST_RX_PKT_CNT),
- ICSSG_PA_STATS(FW_HOST_TX_PKT_CNT),
- ICSSG_PA_STATS(FW_HOST_EGRESS_Q_PRE_OVERFLOW),
- ICSSG_PA_STATS(FW_HOST_EGRESS_Q_EXP_OVERFLOW),
+ /* Firmware-specific stats: exposed via ethtool -S only */
+ ICSSG_PA_STATS(FW_RTU_PKT_DROP, false),
+ ICSSG_PA_STATS(FW_Q0_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q1_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q2_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q3_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q4_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q5_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q6_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_Q7_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_DROPPED_PKT, false),
+ ICSSG_PA_STATS(FW_RX_ERROR, false),
+ ICSSG_PA_STATS(FW_RX_DS_INVALID, false),
+ ICSSG_PA_STATS(FW_TX_DROPPED_PACKET, false),
+ ICSSG_PA_STATS(FW_TX_TS_DROPPED_PACKET, false),
+ ICSSG_PA_STATS(FW_INF_PORT_DISABLED, false),
+ ICSSG_PA_STATS(FW_INF_SAV, false),
+ ICSSG_PA_STATS(FW_INF_SA_DL, false),
+ ICSSG_PA_STATS(FW_INF_PORT_BLOCKED, false),
+ ICSSG_PA_STATS(FW_INF_DROP_TAGGED, false),
+ ICSSG_PA_STATS(FW_INF_DROP_PRIOTAGGED, false),
+ ICSSG_PA_STATS(FW_INF_DROP_NOTAG, false),
+ ICSSG_PA_STATS(FW_INF_DROP_NOTMEMBER, false),
+ ICSSG_PA_STATS(FW_RX_EOF_SHORT_FRMERR, false),
+ ICSSG_PA_STATS(FW_RX_B0_DROP_EARLY_EOF, false),
+ ICSSG_PA_STATS(FW_TX_JUMBO_FRM_CUTOFF, false),
+ ICSSG_PA_STATS(FW_RX_EXP_FRAG_Q_DROP, false),
+ ICSSG_PA_STATS(FW_RX_FIFO_OVERRUN, false),
+ ICSSG_PA_STATS(FW_CUT_THR_PKT, false),
+ ICSSG_PA_STATS(FW_HOST_RX_PKT_CNT, false),
+ ICSSG_PA_STATS(FW_HOST_TX_PKT_CNT, false),
+ ICSSG_PA_STATS(FW_HOST_EGRESS_Q_PRE_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_HOST_EGRESS_Q_EXP_OVERFLOW, false),
+ ICSSG_PA_STATS(FW_HSR_FWD_CHECK_FAIL_DROP, false),
+ ICSSG_PA_STATS(FW_HSR_HE_CHECK_FAIL_DROP, false),
+ ICSSG_PA_STATS(FW_HSR_SKIP_HOST_DUP_DISCARD, false),
+ ICSSG_PA_STATS(FW_LRE_CNT_UNIQUE_RX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_DUPLICATE_RX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_MULTIPLE_RX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_RX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_TX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_OWN_RX, true),
+ ICSSG_PA_STATS(FW_LRE_CNT_ERRWRONGLAN, true),
};
static_assert(ARRAY_SIZE(icssg_all_pa_stats) == ICSSG_NUM_PA_STATS);
diff --git a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
index 7e053b8af3ece..556facb33e0ce 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
@@ -266,5 +266,15 @@
#define FW_HOST_TX_PKT_CNT 0x0250
#define FW_HOST_EGRESS_Q_PRE_OVERFLOW 0x0258
#define FW_HOST_EGRESS_Q_EXP_OVERFLOW 0x0260
+#define FW_HSR_FWD_CHECK_FAIL_DROP 0x0500
+#define FW_HSR_HE_CHECK_FAIL_DROP 0x0508
+#define FW_HSR_SKIP_HOST_DUP_DISCARD 0x0510
+#define FW_LRE_CNT_UNIQUE_RX 0x0518
+#define FW_LRE_CNT_DUPLICATE_RX 0x0520
+#define FW_LRE_CNT_MULTIPLE_RX 0x0528
+#define FW_LRE_CNT_RX 0x0530
+#define FW_LRE_CNT_TX 0x0538
+#define FW_LRE_CNT_OWN_RX 0x0540
+#define FW_LRE_CNT_ERRWRONGLAN 0x0548
#endif /* __NET_TI_ICSSG_SWITCH_MAP_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread