162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include "netlink.h"
462306a36Sopenharmony_ci#include "common.h"
562306a36Sopenharmony_ci#include "bitset.h"
662306a36Sopenharmony_ci
762306a36Sopenharmony_cistruct fec_req_info {
862306a36Sopenharmony_ci	struct ethnl_req_info		base;
962306a36Sopenharmony_ci};
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cistruct fec_reply_data {
1262306a36Sopenharmony_ci	struct ethnl_reply_data		base;
1362306a36Sopenharmony_ci	__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes);
1462306a36Sopenharmony_ci	u32 active_fec;
1562306a36Sopenharmony_ci	u8 fec_auto;
1662306a36Sopenharmony_ci	struct fec_stat_grp {
1762306a36Sopenharmony_ci		u64 stats[1 + ETHTOOL_MAX_LANES];
1862306a36Sopenharmony_ci		u8 cnt;
1962306a36Sopenharmony_ci	} corr, uncorr, corr_bits;
2062306a36Sopenharmony_ci};
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define FEC_REPDATA(__reply_base) \
2362306a36Sopenharmony_ci	container_of(__reply_base, struct fec_reply_data, base)
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define ETHTOOL_FEC_MASK	((ETHTOOL_FEC_LLRS << 1) - 1)
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ciconst struct nla_policy ethnl_fec_get_policy[ETHTOOL_A_FEC_HEADER + 1] = {
2862306a36Sopenharmony_ci	[ETHTOOL_A_FEC_HEADER]	= NLA_POLICY_NESTED(ethnl_header_policy_stats),
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic void
3262306a36Sopenharmony_ciethtool_fec_to_link_modes(u32 fec, unsigned long *link_modes, u8 *fec_auto)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	if (fec_auto)
3562306a36Sopenharmony_ci		*fec_auto = !!(fec & ETHTOOL_FEC_AUTO);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (fec & ETHTOOL_FEC_OFF)
3862306a36Sopenharmony_ci		__set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes);
3962306a36Sopenharmony_ci	if (fec & ETHTOOL_FEC_RS)
4062306a36Sopenharmony_ci		__set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes);
4162306a36Sopenharmony_ci	if (fec & ETHTOOL_FEC_BASER)
4262306a36Sopenharmony_ci		__set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes);
4362306a36Sopenharmony_ci	if (fec & ETHTOOL_FEC_LLRS)
4462306a36Sopenharmony_ci		__set_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int
4862306a36Sopenharmony_ciethtool_link_modes_to_fecparam(struct ethtool_fecparam *fec,
4962306a36Sopenharmony_ci			       unsigned long *link_modes, u8 fec_auto)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	memset(fec, 0, sizeof(*fec));
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (fec_auto)
5462306a36Sopenharmony_ci		fec->fec |= ETHTOOL_FEC_AUTO;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes))
5762306a36Sopenharmony_ci		fec->fec |= ETHTOOL_FEC_OFF;
5862306a36Sopenharmony_ci	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes))
5962306a36Sopenharmony_ci		fec->fec |= ETHTOOL_FEC_RS;
6062306a36Sopenharmony_ci	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes))
6162306a36Sopenharmony_ci		fec->fec |= ETHTOOL_FEC_BASER;
6262306a36Sopenharmony_ci	if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes))
6362306a36Sopenharmony_ci		fec->fec |= ETHTOOL_FEC_LLRS;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (!bitmap_empty(link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS))
6662306a36Sopenharmony_ci		return -EINVAL;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	return 0;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void
7262306a36Sopenharmony_cifec_stats_recalc(struct fec_stat_grp *grp, struct ethtool_fec_stat *stats)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	int i;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (stats->lanes[0] == ETHTOOL_STAT_NOT_SET) {
7762306a36Sopenharmony_ci		grp->stats[0] = stats->total;
7862306a36Sopenharmony_ci		grp->cnt = stats->total != ETHTOOL_STAT_NOT_SET;
7962306a36Sopenharmony_ci		return;
8062306a36Sopenharmony_ci	}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	grp->cnt = 1;
8362306a36Sopenharmony_ci	grp->stats[0] = 0;
8462306a36Sopenharmony_ci	for (i = 0; i < ETHTOOL_MAX_LANES; i++) {
8562306a36Sopenharmony_ci		if (stats->lanes[i] == ETHTOOL_STAT_NOT_SET)
8662306a36Sopenharmony_ci			break;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		grp->stats[0] += stats->lanes[i];
8962306a36Sopenharmony_ci		grp->stats[grp->cnt++] = stats->lanes[i];
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int fec_prepare_data(const struct ethnl_req_info *req_base,
9462306a36Sopenharmony_ci			    struct ethnl_reply_data *reply_base,
9562306a36Sopenharmony_ci			    const struct genl_info *info)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	__ETHTOOL_DECLARE_LINK_MODE_MASK(active_fec_modes) = {};
9862306a36Sopenharmony_ci	struct fec_reply_data *data = FEC_REPDATA(reply_base);
9962306a36Sopenharmony_ci	struct net_device *dev = reply_base->dev;
10062306a36Sopenharmony_ci	struct ethtool_fecparam fec = {};
10162306a36Sopenharmony_ci	int ret;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (!dev->ethtool_ops->get_fecparam)
10462306a36Sopenharmony_ci		return -EOPNOTSUPP;
10562306a36Sopenharmony_ci	ret = ethnl_ops_begin(dev);
10662306a36Sopenharmony_ci	if (ret < 0)
10762306a36Sopenharmony_ci		return ret;
10862306a36Sopenharmony_ci	ret = dev->ethtool_ops->get_fecparam(dev, &fec);
10962306a36Sopenharmony_ci	if (ret)
11062306a36Sopenharmony_ci		goto out_complete;
11162306a36Sopenharmony_ci	if (req_base->flags & ETHTOOL_FLAG_STATS &&
11262306a36Sopenharmony_ci	    dev->ethtool_ops->get_fec_stats) {
11362306a36Sopenharmony_ci		struct ethtool_fec_stats stats;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		ethtool_stats_init((u64 *)&stats, sizeof(stats) / 8);
11662306a36Sopenharmony_ci		dev->ethtool_ops->get_fec_stats(dev, &stats);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		fec_stats_recalc(&data->corr, &stats.corrected_blocks);
11962306a36Sopenharmony_ci		fec_stats_recalc(&data->uncorr, &stats.uncorrectable_blocks);
12062306a36Sopenharmony_ci		fec_stats_recalc(&data->corr_bits, &stats.corrected_bits);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	WARN_ON_ONCE(fec.reserved);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	ethtool_fec_to_link_modes(fec.fec, data->fec_link_modes,
12662306a36Sopenharmony_ci				  &data->fec_auto);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	ethtool_fec_to_link_modes(fec.active_fec, active_fec_modes, NULL);
12962306a36Sopenharmony_ci	data->active_fec = find_first_bit(active_fec_modes,
13062306a36Sopenharmony_ci					  __ETHTOOL_LINK_MODE_MASK_NBITS);
13162306a36Sopenharmony_ci	/* Don't report attr if no FEC mode set. Note that
13262306a36Sopenharmony_ci	 * ethtool_fecparam_to_link_modes() ignores NONE and AUTO.
13362306a36Sopenharmony_ci	 */
13462306a36Sopenharmony_ci	if (data->active_fec == __ETHTOOL_LINK_MODE_MASK_NBITS)
13562306a36Sopenharmony_ci		data->active_fec = 0;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ciout_complete:
13862306a36Sopenharmony_ci	ethnl_ops_complete(dev);
13962306a36Sopenharmony_ci	return ret;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int fec_reply_size(const struct ethnl_req_info *req_base,
14362306a36Sopenharmony_ci			  const struct ethnl_reply_data *reply_base)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
14662306a36Sopenharmony_ci	const struct fec_reply_data *data = FEC_REPDATA(reply_base);
14762306a36Sopenharmony_ci	int len = 0;
14862306a36Sopenharmony_ci	int ret;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	ret = ethnl_bitset_size(data->fec_link_modes, NULL,
15162306a36Sopenharmony_ci				__ETHTOOL_LINK_MODE_MASK_NBITS,
15262306a36Sopenharmony_ci				link_mode_names, compact);
15362306a36Sopenharmony_ci	if (ret < 0)
15462306a36Sopenharmony_ci		return ret;
15562306a36Sopenharmony_ci	len += ret;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)) +	/* _FEC_AUTO */
15862306a36Sopenharmony_ci	       nla_total_size(sizeof(u32));	/* _FEC_ACTIVE */
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (req_base->flags & ETHTOOL_FLAG_STATS)
16162306a36Sopenharmony_ci		len += 3 * nla_total_size_64bit(sizeof(u64) *
16262306a36Sopenharmony_ci						(1 + ETHTOOL_MAX_LANES));
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return len;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int fec_put_stats(struct sk_buff *skb, const struct fec_reply_data *data)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct nlattr *nest;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	nest = nla_nest_start(skb, ETHTOOL_A_FEC_STATS);
17262306a36Sopenharmony_ci	if (!nest)
17362306a36Sopenharmony_ci		return -EMSGSIZE;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_CORRECTED,
17662306a36Sopenharmony_ci			  sizeof(u64) * data->corr.cnt,
17762306a36Sopenharmony_ci			  data->corr.stats, ETHTOOL_A_FEC_STAT_PAD) ||
17862306a36Sopenharmony_ci	    nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_UNCORR,
17962306a36Sopenharmony_ci			  sizeof(u64) * data->uncorr.cnt,
18062306a36Sopenharmony_ci			  data->uncorr.stats, ETHTOOL_A_FEC_STAT_PAD) ||
18162306a36Sopenharmony_ci	    nla_put_64bit(skb, ETHTOOL_A_FEC_STAT_CORR_BITS,
18262306a36Sopenharmony_ci			  sizeof(u64) * data->corr_bits.cnt,
18362306a36Sopenharmony_ci			  data->corr_bits.stats, ETHTOOL_A_FEC_STAT_PAD))
18462306a36Sopenharmony_ci		goto err_cancel;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	nla_nest_end(skb, nest);
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cierr_cancel:
19062306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
19162306a36Sopenharmony_ci	return -EMSGSIZE;
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic int fec_fill_reply(struct sk_buff *skb,
19562306a36Sopenharmony_ci			  const struct ethnl_req_info *req_base,
19662306a36Sopenharmony_ci			  const struct ethnl_reply_data *reply_base)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
19962306a36Sopenharmony_ci	const struct fec_reply_data *data = FEC_REPDATA(reply_base);
20062306a36Sopenharmony_ci	int ret;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	ret = ethnl_put_bitset(skb, ETHTOOL_A_FEC_MODES,
20362306a36Sopenharmony_ci			       data->fec_link_modes, NULL,
20462306a36Sopenharmony_ci			       __ETHTOOL_LINK_MODE_MASK_NBITS,
20562306a36Sopenharmony_ci			       link_mode_names, compact);
20662306a36Sopenharmony_ci	if (ret < 0)
20762306a36Sopenharmony_ci		return ret;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	if (nla_put_u8(skb, ETHTOOL_A_FEC_AUTO, data->fec_auto) ||
21062306a36Sopenharmony_ci	    (data->active_fec &&
21162306a36Sopenharmony_ci	     nla_put_u32(skb, ETHTOOL_A_FEC_ACTIVE, data->active_fec)))
21262306a36Sopenharmony_ci		return -EMSGSIZE;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	if (req_base->flags & ETHTOOL_FLAG_STATS && fec_put_stats(skb, data))
21562306a36Sopenharmony_ci		return -EMSGSIZE;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return 0;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci/* FEC_SET */
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ciconst struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1] = {
22362306a36Sopenharmony_ci	[ETHTOOL_A_FEC_HEADER]	= NLA_POLICY_NESTED(ethnl_header_policy),
22462306a36Sopenharmony_ci	[ETHTOOL_A_FEC_MODES]	= { .type = NLA_NESTED },
22562306a36Sopenharmony_ci	[ETHTOOL_A_FEC_AUTO]	= NLA_POLICY_MAX(NLA_U8, 1),
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic int
22962306a36Sopenharmony_ciethnl_set_fec_validate(struct ethnl_req_info *req_info, struct genl_info *info)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return ops->get_fecparam && ops->set_fecparam ? 1 : -EOPNOTSUPP;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int
23762306a36Sopenharmony_ciethnl_set_fec(struct ethnl_req_info *req_info, struct genl_info *info)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes) = {};
24062306a36Sopenharmony_ci	struct net_device *dev = req_info->dev;
24162306a36Sopenharmony_ci	struct nlattr **tb = info->attrs;
24262306a36Sopenharmony_ci	struct ethtool_fecparam fec = {};
24362306a36Sopenharmony_ci	bool mod = false;
24462306a36Sopenharmony_ci	u8 fec_auto;
24562306a36Sopenharmony_ci	int ret;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	ret = dev->ethtool_ops->get_fecparam(dev, &fec);
24862306a36Sopenharmony_ci	if (ret < 0)
24962306a36Sopenharmony_ci		return ret;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	ethtool_fec_to_link_modes(fec.fec, fec_link_modes, &fec_auto);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	ret = ethnl_update_bitset(fec_link_modes,
25462306a36Sopenharmony_ci				  __ETHTOOL_LINK_MODE_MASK_NBITS,
25562306a36Sopenharmony_ci				  tb[ETHTOOL_A_FEC_MODES],
25662306a36Sopenharmony_ci				  link_mode_names, info->extack, &mod);
25762306a36Sopenharmony_ci	if (ret < 0)
25862306a36Sopenharmony_ci		return ret;
25962306a36Sopenharmony_ci	ethnl_update_u8(&fec_auto, tb[ETHTOOL_A_FEC_AUTO], &mod);
26062306a36Sopenharmony_ci	if (!mod)
26162306a36Sopenharmony_ci		return 0;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	ret = ethtool_link_modes_to_fecparam(&fec, fec_link_modes, fec_auto);
26462306a36Sopenharmony_ci	if (ret) {
26562306a36Sopenharmony_ci		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
26662306a36Sopenharmony_ci				    "invalid FEC modes requested");
26762306a36Sopenharmony_ci		return ret;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci	if (!fec.fec) {
27062306a36Sopenharmony_ci		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
27162306a36Sopenharmony_ci				    "no FEC modes set");
27262306a36Sopenharmony_ci		return -EINVAL;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	ret = dev->ethtool_ops->set_fecparam(dev, &fec);
27662306a36Sopenharmony_ci	return ret < 0 ? ret : 1;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_fec_request_ops = {
28062306a36Sopenharmony_ci	.request_cmd		= ETHTOOL_MSG_FEC_GET,
28162306a36Sopenharmony_ci	.reply_cmd		= ETHTOOL_MSG_FEC_GET_REPLY,
28262306a36Sopenharmony_ci	.hdr_attr		= ETHTOOL_A_FEC_HEADER,
28362306a36Sopenharmony_ci	.req_info_size		= sizeof(struct fec_req_info),
28462306a36Sopenharmony_ci	.reply_data_size	= sizeof(struct fec_reply_data),
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	.prepare_data		= fec_prepare_data,
28762306a36Sopenharmony_ci	.reply_size		= fec_reply_size,
28862306a36Sopenharmony_ci	.fill_reply		= fec_fill_reply,
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	.set_validate		= ethnl_set_fec_validate,
29162306a36Sopenharmony_ci	.set			= ethnl_set_fec,
29262306a36Sopenharmony_ci	.set_ntf_cmd		= ETHTOOL_MSG_FEC_NTF,
29362306a36Sopenharmony_ci};
294