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 --- 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 #include #include +#include +#include #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