diff options
| author | Vadim Fedorenko <vadim.fedorenko@linux.dev> | 2025-09-24 12:40:33 +0000 | 
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2025-09-26 16:49:18 -0700 | 
| commit | cc2f08129925b437bf28f7f7822f20dac083a87c (patch) | |
| tree | dfcf1f26847c4a67e4b1fde8a8a2ad8bb834abe8 | |
| parent | fbb8bc408027a94b0b513410df15003e6ba6a77c (diff) | |
ethtool: add FEC bins histogram report
IEEE 802.3ck-2022 defines counters for FEC bins and 802.3df-2024
clarifies it a bit further. Implement reporting interface through as
addition to FEC stats available in ethtool. Drivers can leave bin
counter uninitialized if per-lane values are provided. In this case the
core will recalculate summ for the bin.
Signed-off-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Link: https://patch.msgid.link/20250924124037.1508846-2-vadim.fedorenko@linux.dev
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | Documentation/netlink/specs/ethtool.yaml | 29 | ||||
| -rw-r--r-- | Documentation/networking/ethtool-netlink.rst | 5 | ||||
| -rw-r--r-- | drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/fungible/funeth/funeth_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/intel/ice/ice_ethtool.c | 4 | ||||
| -rw-r--r-- | drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/sfc/ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/sfc/siena/ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/netdevsim/ethtool.c | 25 | ||||
| -rw-r--r-- | include/linux/ethtool.h | 25 | ||||
| -rw-r--r-- | include/uapi/linux/ethtool_netlink_generated.h | 12 | ||||
| -rw-r--r-- | net/ethtool/fec.c | 75 | 
15 files changed, 186 insertions, 13 deletions
| diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 7a7594713f1f..6a0fb1974513 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -1220,6 +1220,30 @@ attribute-sets:          type: nest          nested-attributes: tunnel-udp    - +    name: fec-hist +    attr-cnt-name: --ethtool-a-fec-hist-cnt +    attributes: +      - +        name: pad +        type: pad +      - +        name: bin-low +        type: u32 +        doc: Low bound of FEC bin (inclusive) +      - +        name: bin-high +        type: u32 +        doc: High bound of FEC bin (inclusive) +      - +        name: bin-val +        type: uint +        doc: Error count in the bin (optional if per-lane values exist) +      - +        name: bin-val-per-lane +        type: binary +        sub-type: u64 +        doc: An array of per-lane error counters in the bin (optional) +  -      name: fec-stat      attr-cnt-name: __ethtool-a-fec-stat-cnt      attributes: @@ -1242,6 +1266,11 @@ attribute-sets:          name: corr-bits          type: binary          sub-type: u64 +      - +        name: hist +        type: nest +        multi-attr: True +        nested-attributes: fec-hist    -      name: fec      attr-cnt-name: __ethtool-a-fec-cnt diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index ab20c644af24..b270886c5f5d 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -1541,6 +1541,11 @@ Drivers fill in the statistics in the following structure:  .. kernel-doc:: include/linux/ethtool.h      :identifiers: ethtool_fec_stats +Statistics may have FEC bins histogram attribute ``ETHTOOL_A_FEC_STAT_HIST`` +as defined in IEEE 802.3ck-2022 and 802.3df-2024. Nested attributes will have +the range of FEC errors in the bin (inclusive) and the amount of error events +in the bin. +  FEC_SET  ======= diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index be32ef8f5c96..41686a6f84b5 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -3208,7 +3208,8 @@ static int bnxt_get_fecparam(struct net_device *dev,  }  static void bnxt_get_fec_stats(struct net_device *dev, -			       struct ethtool_fec_stats *fec_stats) +			       struct ethtool_fec_stats *fec_stats, +			       struct ethtool_fec_hist *hist)  {  	struct bnxt *bp = netdev_priv(dev);  	u64 *rx; diff --git a/drivers/net/ethernet/fungible/funeth/funeth_ethtool.c b/drivers/net/ethernet/fungible/funeth/funeth_ethtool.c index ba83dbf4ed22..1966dba512f8 100644 --- a/drivers/net/ethernet/fungible/funeth/funeth_ethtool.c +++ b/drivers/net/ethernet/fungible/funeth/funeth_ethtool.c @@ -930,7 +930,8 @@ static void fun_get_rmon_stats(struct net_device *netdev,  }  static void fun_get_fec_stats(struct net_device *netdev, -			      struct ethtool_fec_stats *stats) +			      struct ethtool_fec_stats *stats, +			      struct ethtool_fec_hist *hist)  {  	const struct funeth_priv *fp = netdev_priv(netdev); diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index a752d0e3db3a..a5eefa28454c 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -1659,7 +1659,8 @@ static void hns3_set_msglevel(struct net_device *netdev, u32 msg_level)  }  static void hns3_get_fec_stats(struct net_device *netdev, -			       struct ethtool_fec_stats *fec_stats) +			       struct ethtool_fec_stats *fec_stats, +			       struct ethtool_fec_hist *hist)  {  	struct hnae3_handle *handle = hns3_get_handle(netdev);  	struct hnae3_ae_dev *ae_dev = hns3_get_ae_dev(handle); diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 348acd46a0ef..dc131779d426 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -4624,10 +4624,12 @@ static int ice_get_port_fec_stats(struct ice_hw *hw, u16 pcs_quad, u16 pcs_port,   * ice_get_fec_stats - returns FEC correctable, uncorrectable stats per netdev   * @netdev: network interface device structure   * @fec_stats: buffer to hold FEC statistics for given port + * @hist: buffer to put FEC histogram statistics for given port   *   */  static void ice_get_fec_stats(struct net_device *netdev, -			      struct ethtool_fec_stats *fec_stats) +			      struct ethtool_fec_stats *fec_stats, +			      struct ethtool_fec_hist *hist)  {  	struct ice_netdev_priv *np = netdev_priv(netdev);  	struct ice_port_topology port_topology; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c index 998c734ff839..b90e23dc49de 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c @@ -1283,7 +1283,8 @@ end:  }  static void otx2_get_fec_stats(struct net_device *netdev, -			       struct ethtool_fec_stats *fec_stats) +			       struct ethtool_fec_stats *fec_stats, +			       struct ethtool_fec_hist *hist)  {  	struct otx2_nic *pfvf = netdev_priv(netdev);  	struct cgx_fw_data *rsp; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index d507366d773e..bcc3bbb78cc9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1927,7 +1927,8 @@ static int mlx5e_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)  }  static void mlx5e_get_fec_stats(struct net_device *netdev, -				struct ethtool_fec_stats *fec_stats) +				struct ethtool_fec_stats *fec_stats, +				struct ethtool_fec_hist *hist)  {  	struct mlx5e_priv *priv = netdev_priv(netdev); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index fecb8c602024..d55d2ac1c3b9 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -1718,7 +1718,8 @@ fbnic_get_pause_stats(struct net_device *netdev,  static void  fbnic_get_fec_stats(struct net_device *netdev, -		    struct ethtool_fec_stats *fec_stats) +		    struct ethtool_fec_stats *fec_stats, +		    struct ethtool_fec_hist *hist)  {  	struct fbnic_net *fbn = netdev_priv(netdev);  	struct fbnic_phy_stats *phy_stats; diff --git a/drivers/net/ethernet/sfc/ethtool.c b/drivers/net/ethernet/sfc/ethtool.c index 23c6a7df78d0..18fe5850a978 100644 --- a/drivers/net/ethernet/sfc/ethtool.c +++ b/drivers/net/ethernet/sfc/ethtool.c @@ -217,7 +217,8 @@ static int efx_ethtool_set_wol(struct net_device *net_dev,  }  static void efx_ethtool_get_fec_stats(struct net_device *net_dev, -				      struct ethtool_fec_stats *fec_stats) +				      struct ethtool_fec_stats *fec_stats, +				      struct ethtool_fec_hist *hist)  {  	struct efx_nic *efx = efx_netdev_priv(net_dev); diff --git a/drivers/net/ethernet/sfc/siena/ethtool.c b/drivers/net/ethernet/sfc/siena/ethtool.c index 994909789bfe..8c3ebd0617fb 100644 --- a/drivers/net/ethernet/sfc/siena/ethtool.c +++ b/drivers/net/ethernet/sfc/siena/ethtool.c @@ -217,7 +217,8 @@ static int efx_ethtool_set_wol(struct net_device *net_dev,  }  static void efx_ethtool_get_fec_stats(struct net_device *net_dev, -				      struct ethtool_fec_stats *fec_stats) +				      struct ethtool_fec_stats *fec_stats, +				      struct ethtool_fec_hist *hist)  {  	struct efx_nic *efx = netdev_priv(net_dev); diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c index f631d90c428a..36a201533aae 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -165,11 +165,34 @@ nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)  	return 0;  } +static const struct ethtool_fec_hist_range netdevsim_fec_ranges[] = { +	{ 0, 0}, +	{ 1, 3}, +	{ 4, 7}, +	{ 0, 0} +}; +  static void -nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats) +nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats, +		   struct ethtool_fec_hist *hist)  { +	struct ethtool_fec_hist_value *values = hist->values; + +	hist->ranges = netdevsim_fec_ranges; +  	fec_stats->corrected_blocks.total = 123;  	fec_stats->uncorrectable_blocks.total = 4; + +	values[0].per_lane[0] = 125; +	values[0].per_lane[1] = 120; +	values[0].per_lane[2] = 100; +	values[0].per_lane[3] = 100; +	values[1].sum = 12; +	values[2].sum = 2; +	values[2].per_lane[0] = 2; +	values[2].per_lane[1] = 0; +	values[2].per_lane[2] = 0; +	values[2].per_lane[3] = 0;  }  static int nsim_get_ts_info(struct net_device *dev, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index c869b7f8bce8..c2d8b4ec62eb 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -492,7 +492,29 @@ struct ethtool_pause_stats {  };  #define ETHTOOL_MAX_LANES	8 +/** + * IEEE 802.3ck/df defines 16 bins for FEC histogram plus one more for + * the end-of-list marker, total 17 items + */ +#define ETHTOOL_FEC_HIST_MAX	17 +/** + * struct ethtool_fec_hist_range - error bits range for FEC histogram + * statistics + * @low: low bound of the bin (inclusive) + * @high: high bound of the bin (inclusive) + */ +struct ethtool_fec_hist_range { +	u16 low; +	u16 high; +}; +struct ethtool_fec_hist { +	struct ethtool_fec_hist_value { +		u64 sum; +		u64 per_lane[ETHTOOL_MAX_LANES]; +	} values[ETHTOOL_FEC_HIST_MAX]; +	const struct ethtool_fec_hist_range *ranges; +};  /**   * struct ethtool_fec_stats - statistics for IEEE 802.3 FEC   * @corrected_blocks: number of received blocks corrected by FEC @@ -1214,7 +1236,8 @@ struct ethtool_ops {  	int	(*set_link_ksettings)(struct net_device *,  				      const struct ethtool_link_ksettings *);  	void	(*get_fec_stats)(struct net_device *dev, -				 struct ethtool_fec_stats *fec_stats); +				 struct ethtool_fec_stats *fec_stats, +				 struct ethtool_fec_hist *hist);  	int	(*get_fecparam)(struct net_device *,  				      struct ethtool_fecparam *);  	int	(*set_fecparam)(struct net_device *, diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index e3b8813465d7..0e8ac0d974e2 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -562,11 +562,23 @@ enum {  };  enum { +	ETHTOOL_A_FEC_HIST_PAD = 1, +	ETHTOOL_A_FEC_HIST_BIN_LOW, +	ETHTOOL_A_FEC_HIST_BIN_HIGH, +	ETHTOOL_A_FEC_HIST_BIN_VAL, +	ETHTOOL_A_FEC_HIST_BIN_VAL_PER_LANE, + +	__ETHTOOL_A_FEC_HIST_CNT, +	ETHTOOL_A_FEC_HIST_MAX = (__ETHTOOL_A_FEC_HIST_CNT - 1) +}; + +enum {  	ETHTOOL_A_FEC_STAT_UNSPEC,  	ETHTOOL_A_FEC_STAT_PAD,  	ETHTOOL_A_FEC_STAT_CORRECTED,  	ETHTOOL_A_FEC_STAT_UNCORR,  	ETHTOOL_A_FEC_STAT_CORR_BITS, +	ETHTOOL_A_FEC_STAT_HIST,  	__ETHTOOL_A_FEC_STAT_CNT,  	ETHTOOL_A_FEC_STAT_MAX = (__ETHTOOL_A_FEC_STAT_CNT - 1) diff --git a/net/ethtool/fec.c b/net/ethtool/fec.c index e7d3f2c352a3..4669e74cbcaa 100644 --- a/net/ethtool/fec.c +++ b/net/ethtool/fec.c @@ -17,6 +17,7 @@ struct fec_reply_data {  		u64 stats[1 + ETHTOOL_MAX_LANES];  		u8 cnt;  	} corr, uncorr, corr_bits; +	struct ethtool_fec_hist fec_stat_hist;  };  #define FEC_REPDATA(__reply_base) \ @@ -113,7 +114,10 @@ static int fec_prepare_data(const struct ethnl_req_info *req_base,  		struct ethtool_fec_stats stats;  		ethtool_stats_init((u64 *)&stats, sizeof(stats) / 8); -		dev->ethtool_ops->get_fec_stats(dev, &stats); +		ethtool_stats_init((u64 *)data->fec_stat_hist.values, +				   sizeof(data->fec_stat_hist.values) / 8); +		dev->ethtool_ops->get_fec_stats(dev, &stats, +						&data->fec_stat_hist);  		fec_stats_recalc(&data->corr, &stats.corrected_blocks);  		fec_stats_recalc(&data->uncorr, &stats.uncorrectable_blocks); @@ -157,13 +161,77 @@ static int fec_reply_size(const struct ethnl_req_info *req_base,  	len += nla_total_size(sizeof(u8)) +	/* _FEC_AUTO */  	       nla_total_size(sizeof(u32));	/* _FEC_ACTIVE */ -	if (req_base->flags & ETHTOOL_FLAG_STATS) +	if (req_base->flags & ETHTOOL_FLAG_STATS) {  		len += 3 * nla_total_size_64bit(sizeof(u64) *  						(1 + ETHTOOL_MAX_LANES)); +		/* add FEC bins information */ +		len += (nla_total_size(0) +  /* _A_FEC_HIST */ +			nla_total_size(4) +  /* _A_FEC_HIST_BIN_LOW */ +			nla_total_size(4) +  /* _A_FEC_HIST_BIN_HI */ +			/* _A_FEC_HIST_BIN_VAL + per-lane values */ +			nla_total_size_64bit(sizeof(u64)) + +			nla_total_size_64bit(sizeof(u64) * ETHTOOL_MAX_LANES)) * +			ETHTOOL_FEC_HIST_MAX; +	}  	return len;  } +static int fec_put_hist(struct sk_buff *skb, +			const struct ethtool_fec_hist *hist) +{ +	const struct ethtool_fec_hist_range *ranges = hist->ranges; +	const struct ethtool_fec_hist_value *values = hist->values; +	struct nlattr *nest; +	int i, j; +	u64 sum; + +	if (!ranges) +		return 0; + +	for (i = 0; i < ETHTOOL_FEC_HIST_MAX; i++) { +		if (i && !ranges[i].low && !ranges[i].high) +			break; + +		if (WARN_ON_ONCE(values[i].sum == ETHTOOL_STAT_NOT_SET && +				 values[i].per_lane[0] == ETHTOOL_STAT_NOT_SET)) +			break; + +		nest = nla_nest_start(skb, ETHTOOL_A_FEC_STAT_HIST); +		if (!nest) +			return -EMSGSIZE; + +		if (nla_put_u32(skb, ETHTOOL_A_FEC_HIST_BIN_LOW, +				ranges[i].low) || +		    nla_put_u32(skb, ETHTOOL_A_FEC_HIST_BIN_HIGH, +				ranges[i].high)) +			goto err_cancel_hist; +		sum = 0; +		for (j = 0; j < ETHTOOL_MAX_LANES; j++) { +			if (values[i].per_lane[j] == ETHTOOL_STAT_NOT_SET) +				break; +			sum += values[i].per_lane[j]; +		} +		if (nla_put_uint(skb, ETHTOOL_A_FEC_HIST_BIN_VAL, +				 values[i].sum == ETHTOOL_STAT_NOT_SET ? +				 sum : values[i].sum)) +			goto err_cancel_hist; +		if (j && nla_put_64bit(skb, ETHTOOL_A_FEC_HIST_BIN_VAL_PER_LANE, +				       sizeof(u64) * j, +				       values[i].per_lane, +				       ETHTOOL_A_FEC_HIST_PAD)) +			goto err_cancel_hist; + +		nla_nest_end(skb, nest); +	} + +	return 0; + +err_cancel_hist: +	nla_nest_cancel(skb, nest); +	return -EMSGSIZE; +} +  static int fec_put_stats(struct sk_buff *skb, const struct fec_reply_data *data)  {  	struct nlattr *nest; @@ -183,6 +251,9 @@ static int fec_put_stats(struct sk_buff *skb, const struct fec_reply_data *data)  			  data->corr_bits.stats, ETHTOOL_A_FEC_STAT_PAD))  		goto err_cancel; +	if (fec_put_hist(skb, &data->fec_stat_hist)) +		goto err_cancel; +  	nla_nest_end(skb, nest);  	return 0; | 
