[PATCH net-next v4 1/3] net: hsr: Add standard LRE stats via RTM_GETSTATS / IFLA_STATS_LINK_XSTATS

MD Danish Anwar danishanwar at ti.com
Thu Jun 11 02:50:33 PDT 2026


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 on slave A to collect offload
counters.

Signed-off-by: MD Danish Anwar <danishanwar at ti.com>
---
 include/linux/if_hsr.h             |  48 +++++++++++
 include/uapi/linux/hsr_netlink.h   |  56 +++++++++++++
 include/uapi/linux/if_link.h       |   2 +
 net/hsr/hsr_netlink.c              | 130 +++++++++++++++++++++++++++--
 tools/include/uapi/linux/if_link.h |   2 +
 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..11458f683624a 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)
@@ -1915,6 +1916,7 @@ enum {
 	IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
 	IFLA_OFFLOAD_XSTATS_HW_S_INFO,	/* HW stats info. A nest */
 	IFLA_OFFLOAD_XSTATS_L3_STATS,	/* struct rtnl_hw_stats64 */
+	IFLA_OFFLOAD_XSTATS_LRE_STATS,	/* struct hsr_lre_stats */
 	__IFLA_OFFLOAD_XSTATS_MAX
 };
 #define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index db0b0af7a6920..31aedf7460a47 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,127 @@ 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_priv *hsr = netdev_priv(dev);
+	struct hsr_lre_stats stats;
+	int s_prividx = *prividx;
+	struct hsr_port *port;
+	struct nlattr *nest;
+	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.
+	 * Use IFLA_OFFLOAD_XSTATS_LRE_STATS, which is the correct identifier
+	 * from enum ifla_offload_xstats that these NDOs expect.
+	 */
+	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_OFFLOAD_XSTATS_LRE_STATS) &&
+		    ops->ndo_get_offload_stats) {
+			err = ops->ndo_get_offload_stats(IFLA_OFFLOAD_XSTATS_LRE_STATS,
+							 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..ed4e6d53ccbab 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)
@@ -1854,6 +1855,7 @@ enum {
 	IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
 	IFLA_OFFLOAD_XSTATS_HW_S_INFO,	/* HW stats info. A nest */
 	IFLA_OFFLOAD_XSTATS_L3_STATS,	/* struct rtnl_hw_stats64 */
+	IFLA_OFFLOAD_XSTATS_LRE_STATS,	/* struct hsr_lre_stats */
 	__IFLA_OFFLOAD_XSTATS_MAX
 };
 #define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
-- 
2.34.1




More information about the linux-arm-kernel mailing list