xref: /openbmc/linux/net/ethtool/fec.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
11e5d1f69SJakub Kicinski // SPDX-License-Identifier: GPL-2.0-only
21e5d1f69SJakub Kicinski 
31e5d1f69SJakub Kicinski #include "netlink.h"
41e5d1f69SJakub Kicinski #include "common.h"
51e5d1f69SJakub Kicinski #include "bitset.h"
61e5d1f69SJakub Kicinski 
71e5d1f69SJakub Kicinski struct fec_req_info {
81e5d1f69SJakub Kicinski 	struct ethnl_req_info		base;
91e5d1f69SJakub Kicinski };
101e5d1f69SJakub Kicinski 
111e5d1f69SJakub Kicinski struct fec_reply_data {
121e5d1f69SJakub Kicinski 	struct ethnl_reply_data		base;
131e5d1f69SJakub Kicinski 	__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes);
141e5d1f69SJakub Kicinski 	u32 active_fec;
151e5d1f69SJakub Kicinski 	u8 fec_auto;
16be85dbfeSJakub Kicinski 	struct fec_stat_grp {
17be85dbfeSJakub Kicinski 		u64 stats[1 + ETHTOOL_MAX_LANES];
18be85dbfeSJakub Kicinski 		u8 cnt;
19be85dbfeSJakub Kicinski 	} corr, uncorr, corr_bits;
201e5d1f69SJakub Kicinski };
211e5d1f69SJakub Kicinski 
221e5d1f69SJakub Kicinski #define FEC_REPDATA(__reply_base) \
231e5d1f69SJakub Kicinski 	container_of(__reply_base, struct fec_reply_data, base)
241e5d1f69SJakub Kicinski 
251e5d1f69SJakub Kicinski #define ETHTOOL_FEC_MASK	((ETHTOOL_FEC_LLRS << 1) - 1)
261e5d1f69SJakub Kicinski 
271e5d1f69SJakub Kicinski const struct nla_policy ethnl_fec_get_policy[ETHTOOL_A_FEC_HEADER + 1] = {
28be85dbfeSJakub Kicinski 	[ETHTOOL_A_FEC_HEADER]	= NLA_POLICY_NESTED(ethnl_header_policy_stats),
291e5d1f69SJakub Kicinski };
301e5d1f69SJakub Kicinski 
311e5d1f69SJakub Kicinski static void
ethtool_fec_to_link_modes(u32 fec,unsigned long * link_modes,u8 * fec_auto)321e5d1f69SJakub Kicinski ethtool_fec_to_link_modes(u32 fec, unsigned long *link_modes, u8 *fec_auto)
331e5d1f69SJakub Kicinski {
341e5d1f69SJakub Kicinski 	if (fec_auto)
351e5d1f69SJakub Kicinski 		*fec_auto = !!(fec & ETHTOOL_FEC_AUTO);
361e5d1f69SJakub Kicinski 
371e5d1f69SJakub Kicinski 	if (fec & ETHTOOL_FEC_OFF)
381e5d1f69SJakub Kicinski 		__set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes);
391e5d1f69SJakub Kicinski 	if (fec & ETHTOOL_FEC_RS)
401e5d1f69SJakub Kicinski 		__set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes);
411e5d1f69SJakub Kicinski 	if (fec & ETHTOOL_FEC_BASER)
421e5d1f69SJakub Kicinski 		__set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes);
431e5d1f69SJakub Kicinski 	if (fec & ETHTOOL_FEC_LLRS)
441e5d1f69SJakub Kicinski 		__set_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes);
451e5d1f69SJakub Kicinski }
461e5d1f69SJakub Kicinski 
471e5d1f69SJakub Kicinski static int
ethtool_link_modes_to_fecparam(struct ethtool_fecparam * fec,unsigned long * link_modes,u8 fec_auto)481e5d1f69SJakub Kicinski ethtool_link_modes_to_fecparam(struct ethtool_fecparam *fec,
491e5d1f69SJakub Kicinski 			       unsigned long *link_modes, u8 fec_auto)
501e5d1f69SJakub Kicinski {
511e5d1f69SJakub Kicinski 	memset(fec, 0, sizeof(*fec));
521e5d1f69SJakub Kicinski 
531e5d1f69SJakub Kicinski 	if (fec_auto)
541e5d1f69SJakub Kicinski 		fec->fec |= ETHTOOL_FEC_AUTO;
551e5d1f69SJakub Kicinski 
561e5d1f69SJakub Kicinski 	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes))
571e5d1f69SJakub Kicinski 		fec->fec |= ETHTOOL_FEC_OFF;
581e5d1f69SJakub Kicinski 	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes))
591e5d1f69SJakub Kicinski 		fec->fec |= ETHTOOL_FEC_RS;
601e5d1f69SJakub Kicinski 	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes))
611e5d1f69SJakub Kicinski 		fec->fec |= ETHTOOL_FEC_BASER;
621e5d1f69SJakub Kicinski 	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes))
631e5d1f69SJakub Kicinski 		fec->fec |= ETHTOOL_FEC_LLRS;
641e5d1f69SJakub Kicinski 
651e5d1f69SJakub Kicinski 	if (!bitmap_empty(link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS))
661e5d1f69SJakub Kicinski 		return -EINVAL;
671e5d1f69SJakub Kicinski 
681e5d1f69SJakub Kicinski 	return 0;
691e5d1f69SJakub Kicinski }
701e5d1f69SJakub Kicinski 
71be85dbfeSJakub Kicinski static void
fec_stats_recalc(struct fec_stat_grp * grp,struct ethtool_fec_stat * stats)72be85dbfeSJakub Kicinski fec_stats_recalc(struct fec_stat_grp *grp, struct ethtool_fec_stat *stats)
73be85dbfeSJakub Kicinski {
74be85dbfeSJakub Kicinski 	int i;
75be85dbfeSJakub Kicinski 
76be85dbfeSJakub Kicinski 	if (stats->lanes[0] == ETHTOOL_STAT_NOT_SET) {
77be85dbfeSJakub Kicinski 		grp->stats[0] = stats->total;
78be85dbfeSJakub Kicinski 		grp->cnt = stats->total != ETHTOOL_STAT_NOT_SET;
79be85dbfeSJakub Kicinski 		return;
80be85dbfeSJakub Kicinski 	}
81be85dbfeSJakub Kicinski 
82be85dbfeSJakub Kicinski 	grp->cnt = 1;
83be85dbfeSJakub Kicinski 	grp->stats[0] = 0;
84be85dbfeSJakub Kicinski 	for (i = 0; i < ETHTOOL_MAX_LANES; i++) {
85be85dbfeSJakub Kicinski 		if (stats->lanes[i] == ETHTOOL_STAT_NOT_SET)
86be85dbfeSJakub Kicinski 			break;
87be85dbfeSJakub Kicinski 
88be85dbfeSJakub Kicinski 		grp->stats[0] += stats->lanes[i];
89be85dbfeSJakub Kicinski 		grp->stats[grp->cnt++] = stats->lanes[i];
90be85dbfeSJakub Kicinski 	}
91be85dbfeSJakub Kicinski }
92be85dbfeSJakub Kicinski 
fec_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)931e5d1f69SJakub Kicinski static int fec_prepare_data(const struct ethnl_req_info *req_base,
941e5d1f69SJakub Kicinski 			    struct ethnl_reply_data *reply_base,
95*f946270dSJakub Kicinski 			    const struct genl_info *info)
961e5d1f69SJakub Kicinski {
971e5d1f69SJakub Kicinski 	__ETHTOOL_DECLARE_LINK_MODE_MASK(active_fec_modes) = {};
981e5d1f69SJakub Kicinski 	struct fec_reply_data *data = FEC_REPDATA(reply_base);
991e5d1f69SJakub Kicinski 	struct net_device *dev = reply_base->dev;
1001e5d1f69SJakub Kicinski 	struct ethtool_fecparam fec = {};
1011e5d1f69SJakub Kicinski 	int ret;
1021e5d1f69SJakub Kicinski 
1031e5d1f69SJakub Kicinski 	if (!dev->ethtool_ops->get_fecparam)
1041e5d1f69SJakub Kicinski 		return -EOPNOTSUPP;
1051e5d1f69SJakub Kicinski 	ret = ethnl_ops_begin(dev);
1061e5d1f69SJakub Kicinski 	if (ret < 0)
1071e5d1f69SJakub Kicinski 		return ret;
1081e5d1f69SJakub Kicinski 	ret = dev->ethtool_ops->get_fecparam(dev, &fec);
1091e5d1f69SJakub Kicinski 	if (ret)
1103d7cc109SJakub Kicinski 		goto out_complete;
111be85dbfeSJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
112be85dbfeSJakub Kicinski 	    dev->ethtool_ops->get_fec_stats) {
113be85dbfeSJakub Kicinski 		struct ethtool_fec_stats stats;
114be85dbfeSJakub Kicinski 
115be85dbfeSJakub Kicinski 		ethtool_stats_init((u64 *)&stats, sizeof(stats) / 8);
116be85dbfeSJakub Kicinski 		dev->ethtool_ops->get_fec_stats(dev, &stats);
117be85dbfeSJakub Kicinski 
118be85dbfeSJakub Kicinski 		fec_stats_recalc(&data->corr, &stats.corrected_blocks);
119be85dbfeSJakub Kicinski 		fec_stats_recalc(&data->uncorr, &stats.uncorrectable_blocks);
120be85dbfeSJakub Kicinski 		fec_stats_recalc(&data->corr_bits, &stats.corrected_bits);
121be85dbfeSJakub Kicinski 	}
1221e5d1f69SJakub Kicinski 
1231e5d1f69SJakub Kicinski 	WARN_ON_ONCE(fec.reserved);
1241e5d1f69SJakub Kicinski 
1251e5d1f69SJakub Kicinski 	ethtool_fec_to_link_modes(fec.fec, data->fec_link_modes,
1261e5d1f69SJakub Kicinski 				  &data->fec_auto);
1271e5d1f69SJakub Kicinski 
1281e5d1f69SJakub Kicinski 	ethtool_fec_to_link_modes(fec.active_fec, active_fec_modes, NULL);
1291e5d1f69SJakub Kicinski 	data->active_fec = find_first_bit(active_fec_modes,
1301e5d1f69SJakub Kicinski 					  __ETHTOOL_LINK_MODE_MASK_NBITS);
1311e5d1f69SJakub Kicinski 	/* Don't report attr if no FEC mode set. Note that
1321e5d1f69SJakub Kicinski 	 * ethtool_fecparam_to_link_modes() ignores NONE and AUTO.
1331e5d1f69SJakub Kicinski 	 */
1341e5d1f69SJakub Kicinski 	if (data->active_fec == __ETHTOOL_LINK_MODE_MASK_NBITS)
1351e5d1f69SJakub Kicinski 		data->active_fec = 0;
1361e5d1f69SJakub Kicinski 
1373d7cc109SJakub Kicinski out_complete:
1383d7cc109SJakub Kicinski 	ethnl_ops_complete(dev);
1393d7cc109SJakub Kicinski 	return ret;
1401e5d1f69SJakub Kicinski }
1411e5d1f69SJakub Kicinski 
fec_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)1421e5d1f69SJakub Kicinski static int fec_reply_size(const struct ethnl_req_info *req_base,
1431e5d1f69SJakub Kicinski 			  const struct ethnl_reply_data *reply_base)
1441e5d1f69SJakub Kicinski {
1451e5d1f69SJakub Kicinski 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
1461e5d1f69SJakub Kicinski 	const struct fec_reply_data *data = FEC_REPDATA(reply_base);
1471e5d1f69SJakub Kicinski 	int len = 0;
1481e5d1f69SJakub Kicinski 	int ret;
1491e5d1f69SJakub Kicinski 
1501e5d1f69SJakub Kicinski 	ret = ethnl_bitset_size(data->fec_link_modes, NULL,
1511e5d1f69SJakub Kicinski 				__ETHTOOL_LINK_MODE_MASK_NBITS,
1521e5d1f69SJakub Kicinski 				link_mode_names, compact);
1531e5d1f69SJakub Kicinski 	if (ret < 0)
1541e5d1f69SJakub Kicinski 		return ret;
1551e5d1f69SJakub Kicinski 	len += ret;
1561e5d1f69SJakub Kicinski 
1571e5d1f69SJakub Kicinski 	len += nla_total_size(sizeof(u8)) +	/* _FEC_AUTO */
1581e5d1f69SJakub Kicinski 	       nla_total_size(sizeof(u32));	/* _FEC_ACTIVE */
1591e5d1f69SJakub Kicinski 
160be85dbfeSJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS)
161be85dbfeSJakub Kicinski 		len += 3 * nla_total_size_64bit(sizeof(u64) *
162be85dbfeSJakub Kicinski 						(1 + ETHTOOL_MAX_LANES));
163be85dbfeSJakub Kicinski 
1641e5d1f69SJakub Kicinski 	return len;
1651e5d1f69SJakub Kicinski }
1661e5d1f69SJakub Kicinski 
fec_put_stats(struct sk_buff * skb,const struct fec_reply_data * data)167be85dbfeSJakub Kicinski static int fec_put_stats(struct sk_buff *skb, const struct fec_reply_data *data)
168be85dbfeSJakub Kicinski {
169be85dbfeSJakub Kicinski 	struct nlattr *nest;
170be85dbfeSJakub Kicinski 
171be85dbfeSJakub Kicinski 	nest = nla_nest_start(skb, ETHTOOL_A_FEC_STATS);
172be85dbfeSJakub Kicinski 	if (!nest)
173be85dbfeSJakub Kicinski 		return -EMSGSIZE;
174be85dbfeSJakub Kicinski 
175be85dbfeSJakub Kicinski 	if (nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_CORRECTED,
176be85dbfeSJakub Kicinski 			  sizeof(u64) * data->corr.cnt,
177be85dbfeSJakub Kicinski 			  data->corr.stats, ETHTOOL_A_FEC_STAT_PAD) ||
178be85dbfeSJakub Kicinski 	    nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_UNCORR,
179be85dbfeSJakub Kicinski 			  sizeof(u64) * data->uncorr.cnt,
180be85dbfeSJakub Kicinski 			  data->uncorr.stats, ETHTOOL_A_FEC_STAT_PAD) ||
181be85dbfeSJakub Kicinski 	    nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_CORR_BITS,
182be85dbfeSJakub Kicinski 			  sizeof(u64) * data->corr_bits.cnt,
183be85dbfeSJakub Kicinski 			  data->corr_bits.stats, ETHTOOL_A_FEC_STAT_PAD))
184be85dbfeSJakub Kicinski 		goto err_cancel;
185be85dbfeSJakub Kicinski 
186be85dbfeSJakub Kicinski 	nla_nest_end(skb, nest);
187be85dbfeSJakub Kicinski 	return 0;
188be85dbfeSJakub Kicinski 
189be85dbfeSJakub Kicinski err_cancel:
190be85dbfeSJakub Kicinski 	nla_nest_cancel(skb, nest);
191be85dbfeSJakub Kicinski 	return -EMSGSIZE;
192be85dbfeSJakub Kicinski }
193be85dbfeSJakub Kicinski 
fec_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)1941e5d1f69SJakub Kicinski static int fec_fill_reply(struct sk_buff *skb,
1951e5d1f69SJakub Kicinski 			  const struct ethnl_req_info *req_base,
1961e5d1f69SJakub Kicinski 			  const struct ethnl_reply_data *reply_base)
1971e5d1f69SJakub Kicinski {
1981e5d1f69SJakub Kicinski 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
1991e5d1f69SJakub Kicinski 	const struct fec_reply_data *data = FEC_REPDATA(reply_base);
2001e5d1f69SJakub Kicinski 	int ret;
2011e5d1f69SJakub Kicinski 
2021e5d1f69SJakub Kicinski 	ret = ethnl_put_bitset(skb, ETHTOOL_A_FEC_MODES,
2031e5d1f69SJakub Kicinski 			       data->fec_link_modes, NULL,
2041e5d1f69SJakub Kicinski 			       __ETHTOOL_LINK_MODE_MASK_NBITS,
2051e5d1f69SJakub Kicinski 			       link_mode_names, compact);
2061e5d1f69SJakub Kicinski 	if (ret < 0)
2071e5d1f69SJakub Kicinski 		return ret;
2081e5d1f69SJakub Kicinski 
2091e5d1f69SJakub Kicinski 	if (nla_put_u8(skb, ETHTOOL_A_FEC_AUTO, data->fec_auto) ||
2101e5d1f69SJakub Kicinski 	    (data->active_fec &&
2111e5d1f69SJakub Kicinski 	     nla_put_u32(skb, ETHTOOL_A_FEC_ACTIVE, data->active_fec)))
2121e5d1f69SJakub Kicinski 		return -EMSGSIZE;
2131e5d1f69SJakub Kicinski 
214be85dbfeSJakub Kicinski 	if (req_base->flags & ETHTOOL_FLAG_STATS && fec_put_stats(skb, data))
215be85dbfeSJakub Kicinski 		return -EMSGSIZE;
216be85dbfeSJakub Kicinski 
2171e5d1f69SJakub Kicinski 	return 0;
2181e5d1f69SJakub Kicinski }
2191e5d1f69SJakub Kicinski 
22004007961SJakub Kicinski /* FEC_SET */
22104007961SJakub Kicinski 
22204007961SJakub Kicinski const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1] = {
22304007961SJakub Kicinski 	[ETHTOOL_A_FEC_HEADER]	= NLA_POLICY_NESTED(ethnl_header_policy),
22404007961SJakub Kicinski 	[ETHTOOL_A_FEC_MODES]	= { .type = NLA_NESTED },
22504007961SJakub Kicinski 	[ETHTOOL_A_FEC_AUTO]	= NLA_POLICY_MAX(NLA_U8, 1),
22604007961SJakub Kicinski };
22704007961SJakub Kicinski 
22804007961SJakub Kicinski static int
ethnl_set_fec_validate(struct ethnl_req_info * req_info,struct genl_info * info)22904007961SJakub Kicinski ethnl_set_fec_validate(struct ethnl_req_info *req_info, struct genl_info *info)
23004007961SJakub Kicinski {
23104007961SJakub Kicinski 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
23204007961SJakub Kicinski 
23304007961SJakub Kicinski 	return ops->get_fecparam && ops->set_fecparam ? 1 : -EOPNOTSUPP;
23404007961SJakub Kicinski }
23504007961SJakub Kicinski 
23604007961SJakub Kicinski static int
ethnl_set_fec(struct ethnl_req_info * req_info,struct genl_info * info)23704007961SJakub Kicinski ethnl_set_fec(struct ethnl_req_info *req_info, struct genl_info *info)
23804007961SJakub Kicinski {
23904007961SJakub Kicinski 	__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes) = {};
24004007961SJakub Kicinski 	struct net_device *dev = req_info->dev;
24104007961SJakub Kicinski 	struct nlattr **tb = info->attrs;
24204007961SJakub Kicinski 	struct ethtool_fecparam fec = {};
24304007961SJakub Kicinski 	bool mod = false;
24404007961SJakub Kicinski 	u8 fec_auto;
24504007961SJakub Kicinski 	int ret;
24604007961SJakub Kicinski 
24704007961SJakub Kicinski 	ret = dev->ethtool_ops->get_fecparam(dev, &fec);
24804007961SJakub Kicinski 	if (ret < 0)
24904007961SJakub Kicinski 		return ret;
25004007961SJakub Kicinski 
25104007961SJakub Kicinski 	ethtool_fec_to_link_modes(fec.fec, fec_link_modes, &fec_auto);
25204007961SJakub Kicinski 
25304007961SJakub Kicinski 	ret = ethnl_update_bitset(fec_link_modes,
25404007961SJakub Kicinski 				  __ETHTOOL_LINK_MODE_MASK_NBITS,
25504007961SJakub Kicinski 				  tb[ETHTOOL_A_FEC_MODES],
25604007961SJakub Kicinski 				  link_mode_names, info->extack, &mod);
25704007961SJakub Kicinski 	if (ret < 0)
25804007961SJakub Kicinski 		return ret;
25904007961SJakub Kicinski 	ethnl_update_u8(&fec_auto, tb[ETHTOOL_A_FEC_AUTO], &mod);
26004007961SJakub Kicinski 	if (!mod)
26104007961SJakub Kicinski 		return 0;
26204007961SJakub Kicinski 
26304007961SJakub Kicinski 	ret = ethtool_link_modes_to_fecparam(&fec, fec_link_modes, fec_auto);
26404007961SJakub Kicinski 	if (ret) {
26504007961SJakub Kicinski 		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
26604007961SJakub Kicinski 				    "invalid FEC modes requested");
26704007961SJakub Kicinski 		return ret;
26804007961SJakub Kicinski 	}
26904007961SJakub Kicinski 	if (!fec.fec) {
27004007961SJakub Kicinski 		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
27104007961SJakub Kicinski 				    "no FEC modes set");
27204007961SJakub Kicinski 		return -EINVAL;
27304007961SJakub Kicinski 	}
27404007961SJakub Kicinski 
27504007961SJakub Kicinski 	ret = dev->ethtool_ops->set_fecparam(dev, &fec);
27604007961SJakub Kicinski 	return ret < 0 ? ret : 1;
27704007961SJakub Kicinski }
27804007961SJakub Kicinski 
2791e5d1f69SJakub Kicinski const struct ethnl_request_ops ethnl_fec_request_ops = {
2801e5d1f69SJakub Kicinski 	.request_cmd		= ETHTOOL_MSG_FEC_GET,
2811e5d1f69SJakub Kicinski 	.reply_cmd		= ETHTOOL_MSG_FEC_GET_REPLY,
2821e5d1f69SJakub Kicinski 	.hdr_attr		= ETHTOOL_A_FEC_HEADER,
2831e5d1f69SJakub Kicinski 	.req_info_size		= sizeof(struct fec_req_info),
2841e5d1f69SJakub Kicinski 	.reply_data_size	= sizeof(struct fec_reply_data),
2851e5d1f69SJakub Kicinski 
2861e5d1f69SJakub Kicinski 	.prepare_data		= fec_prepare_data,
2871e5d1f69SJakub Kicinski 	.reply_size		= fec_reply_size,
2881e5d1f69SJakub Kicinski 	.fill_reply		= fec_fill_reply,
28904007961SJakub Kicinski 
29004007961SJakub Kicinski 	.set_validate		= ethnl_set_fec_validate,
29104007961SJakub Kicinski 	.set			= ethnl_set_fec,
29204007961SJakub Kicinski 	.set_ntf_cmd		= ETHTOOL_MSG_FEC_NTF,
2931e5d1f69SJakub Kicinski };
294