162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2022-2023 NXP
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include "common.h"
662306a36Sopenharmony_ci#include "netlink.h"
762306a36Sopenharmony_ci
862306a36Sopenharmony_cistruct mm_req_info {
962306a36Sopenharmony_ci	struct ethnl_req_info		base;
1062306a36Sopenharmony_ci};
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct mm_reply_data {
1362306a36Sopenharmony_ci	struct ethnl_reply_data		base;
1462306a36Sopenharmony_ci	struct ethtool_mm_state		state;
1562306a36Sopenharmony_ci	struct ethtool_mm_stats		stats;
1662306a36Sopenharmony_ci};
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define MM_REPDATA(__reply_base) \
1962306a36Sopenharmony_ci	container_of(__reply_base, struct mm_reply_data, base)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define ETHTOOL_MM_STAT_CNT \
2262306a36Sopenharmony_ci	(__ETHTOOL_A_MM_STAT_CNT - (ETHTOOL_A_MM_STAT_PAD + 1))
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciconst struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1] = {
2562306a36Sopenharmony_ci	[ETHTOOL_A_MM_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_stats),
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int mm_prepare_data(const struct ethnl_req_info *req_base,
2962306a36Sopenharmony_ci			   struct ethnl_reply_data *reply_base,
3062306a36Sopenharmony_ci			   const struct genl_info *info)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct mm_reply_data *data = MM_REPDATA(reply_base);
3362306a36Sopenharmony_ci	struct net_device *dev = reply_base->dev;
3462306a36Sopenharmony_ci	const struct ethtool_ops *ops;
3562306a36Sopenharmony_ci	int ret;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	ops = dev->ethtool_ops;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	if (!ops->get_mm)
4062306a36Sopenharmony_ci		return -EOPNOTSUPP;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	ethtool_stats_init((u64 *)&data->stats,
4362306a36Sopenharmony_ci			   sizeof(data->stats) / sizeof(u64));
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ret = ethnl_ops_begin(dev);
4662306a36Sopenharmony_ci	if (ret < 0)
4762306a36Sopenharmony_ci		return ret;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	ret = ops->get_mm(dev, &data->state);
5062306a36Sopenharmony_ci	if (ret)
5162306a36Sopenharmony_ci		goto out_complete;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (ops->get_mm_stats && (req_base->flags & ETHTOOL_FLAG_STATS))
5462306a36Sopenharmony_ci		ops->get_mm_stats(dev, &data->stats);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ciout_complete:
5762306a36Sopenharmony_ci	ethnl_ops_complete(dev);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	return ret;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int mm_reply_size(const struct ethnl_req_info *req_base,
6362306a36Sopenharmony_ci			 const struct ethnl_reply_data *reply_base)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	int len = 0;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)); /* _MM_PMAC_ENABLED */
6862306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)); /* _MM_TX_ENABLED */
6962306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)); /* _MM_TX_ACTIVE */
7062306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_ENABLED */
7162306a36Sopenharmony_ci	len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_STATUS */
7262306a36Sopenharmony_ci	len += nla_total_size(sizeof(u32)); /* _MM_VERIFY_TIME */
7362306a36Sopenharmony_ci	len += nla_total_size(sizeof(u32)); /* _MM_MAX_VERIFY_TIME */
7462306a36Sopenharmony_ci	len += nla_total_size(sizeof(u32)); /* _MM_TX_MIN_FRAG_SIZE */
7562306a36Sopenharmony_ci	len += nla_total_size(sizeof(u32)); /* _MM_RX_MIN_FRAG_SIZE */
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (req_base->flags & ETHTOOL_FLAG_STATS)
7862306a36Sopenharmony_ci		len += nla_total_size(0) + /* _MM_STATS */
7962306a36Sopenharmony_ci		       nla_total_size_64bit(sizeof(u64)) * ETHTOOL_MM_STAT_CNT;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return len;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int mm_put_stat(struct sk_buff *skb, u64 val, u16 attrtype)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	if (val == ETHTOOL_STAT_NOT_SET)
8762306a36Sopenharmony_ci		return 0;
8862306a36Sopenharmony_ci	if (nla_put_u64_64bit(skb, attrtype, val, ETHTOOL_A_MM_STAT_PAD))
8962306a36Sopenharmony_ci		return -EMSGSIZE;
9062306a36Sopenharmony_ci	return 0;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int mm_put_stats(struct sk_buff *skb,
9462306a36Sopenharmony_ci			const struct ethtool_mm_stats *stats)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct nlattr *nest;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	nest = nla_nest_start(skb, ETHTOOL_A_MM_STATS);
9962306a36Sopenharmony_ci	if (!nest)
10062306a36Sopenharmony_ci		return -EMSGSIZE;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (mm_put_stat(skb, stats->MACMergeFrameAssErrorCount,
10362306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS) ||
10462306a36Sopenharmony_ci	    mm_put_stat(skb, stats->MACMergeFrameSmdErrorCount,
10562306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_SMD_ERRORS) ||
10662306a36Sopenharmony_ci	    mm_put_stat(skb, stats->MACMergeFrameAssOkCount,
10762306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_REASSEMBLY_OK) ||
10862306a36Sopenharmony_ci	    mm_put_stat(skb, stats->MACMergeFragCountRx,
10962306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_RX_FRAG_COUNT) ||
11062306a36Sopenharmony_ci	    mm_put_stat(skb, stats->MACMergeFragCountTx,
11162306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_TX_FRAG_COUNT) ||
11262306a36Sopenharmony_ci	    mm_put_stat(skb, stats->MACMergeHoldCount,
11362306a36Sopenharmony_ci			ETHTOOL_A_MM_STAT_HOLD_COUNT))
11462306a36Sopenharmony_ci		goto err_cancel;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	nla_nest_end(skb, nest);
11762306a36Sopenharmony_ci	return 0;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cierr_cancel:
12062306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
12162306a36Sopenharmony_ci	return -EMSGSIZE;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int mm_fill_reply(struct sk_buff *skb,
12562306a36Sopenharmony_ci			 const struct ethnl_req_info *req_base,
12662306a36Sopenharmony_ci			 const struct ethnl_reply_data *reply_base)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	const struct mm_reply_data *data = MM_REPDATA(reply_base);
12962306a36Sopenharmony_ci	const struct ethtool_mm_state *state = &data->state;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (nla_put_u8(skb, ETHTOOL_A_MM_TX_ENABLED, state->tx_enabled) ||
13262306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_MM_TX_ACTIVE, state->tx_active) ||
13362306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_MM_PMAC_ENABLED, state->pmac_enabled) ||
13462306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_ENABLED, state->verify_enabled) ||
13562306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_STATUS, state->verify_status) ||
13662306a36Sopenharmony_ci	    nla_put_u32(skb, ETHTOOL_A_MM_VERIFY_TIME, state->verify_time) ||
13762306a36Sopenharmony_ci	    nla_put_u32(skb, ETHTOOL_A_MM_MAX_VERIFY_TIME, state->max_verify_time) ||
13862306a36Sopenharmony_ci	    nla_put_u32(skb, ETHTOOL_A_MM_TX_MIN_FRAG_SIZE, state->tx_min_frag_size) ||
13962306a36Sopenharmony_ci	    nla_put_u32(skb, ETHTOOL_A_MM_RX_MIN_FRAG_SIZE, state->rx_min_frag_size))
14062306a36Sopenharmony_ci		return -EMSGSIZE;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (req_base->flags & ETHTOOL_FLAG_STATS &&
14362306a36Sopenharmony_ci	    mm_put_stats(skb, &data->stats))
14462306a36Sopenharmony_ci		return -EMSGSIZE;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	return 0;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ciconst struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1] = {
15062306a36Sopenharmony_ci	[ETHTOOL_A_MM_HEADER]		= NLA_POLICY_NESTED(ethnl_header_policy),
15162306a36Sopenharmony_ci	[ETHTOOL_A_MM_VERIFY_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
15262306a36Sopenharmony_ci	[ETHTOOL_A_MM_VERIFY_TIME]	= NLA_POLICY_RANGE(NLA_U32, 1, 128),
15362306a36Sopenharmony_ci	[ETHTOOL_A_MM_TX_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
15462306a36Sopenharmony_ci	[ETHTOOL_A_MM_PMAC_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
15562306a36Sopenharmony_ci	[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE]	= NLA_POLICY_RANGE(NLA_U32, 60, 252),
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void mm_state_to_cfg(const struct ethtool_mm_state *state,
15962306a36Sopenharmony_ci			    struct ethtool_mm_cfg *cfg)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	/* We could also compare state->verify_status against
16262306a36Sopenharmony_ci	 * ETHTOOL_MM_VERIFY_STATUS_DISABLED, but state->verify_enabled
16362306a36Sopenharmony_ci	 * is more like an administrative state which should be seen in
16462306a36Sopenharmony_ci	 * ETHTOOL_MSG_MM_GET replies. For example, a port with verification
16562306a36Sopenharmony_ci	 * disabled might be in the ETHTOOL_MM_VERIFY_STATUS_INITIAL
16662306a36Sopenharmony_ci	 * if it's down.
16762306a36Sopenharmony_ci	 */
16862306a36Sopenharmony_ci	cfg->verify_enabled = state->verify_enabled;
16962306a36Sopenharmony_ci	cfg->verify_time = state->verify_time;
17062306a36Sopenharmony_ci	cfg->tx_enabled = state->tx_enabled;
17162306a36Sopenharmony_ci	cfg->pmac_enabled = state->pmac_enabled;
17262306a36Sopenharmony_ci	cfg->tx_min_frag_size = state->tx_min_frag_size;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic int
17662306a36Sopenharmony_ciethnl_set_mm_validate(struct ethnl_req_info *req_info, struct genl_info *info)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	return ops->get_mm && ops->set_mm ? 1 : -EOPNOTSUPP;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int ethnl_set_mm(struct ethnl_req_info *req_info, struct genl_info *info)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct netlink_ext_ack *extack = info->extack;
18662306a36Sopenharmony_ci	struct net_device *dev = req_info->dev;
18762306a36Sopenharmony_ci	struct ethtool_mm_state state = {};
18862306a36Sopenharmony_ci	struct nlattr **tb = info->attrs;
18962306a36Sopenharmony_ci	struct ethtool_mm_cfg cfg = {};
19062306a36Sopenharmony_ci	bool mod = false;
19162306a36Sopenharmony_ci	int ret;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	ret = dev->ethtool_ops->get_mm(dev, &state);
19462306a36Sopenharmony_ci	if (ret)
19562306a36Sopenharmony_ci		return ret;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	mm_state_to_cfg(&state, &cfg);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	ethnl_update_bool(&cfg.verify_enabled, tb[ETHTOOL_A_MM_VERIFY_ENABLED],
20062306a36Sopenharmony_ci			  &mod);
20162306a36Sopenharmony_ci	ethnl_update_u32(&cfg.verify_time, tb[ETHTOOL_A_MM_VERIFY_TIME], &mod);
20262306a36Sopenharmony_ci	ethnl_update_bool(&cfg.tx_enabled, tb[ETHTOOL_A_MM_TX_ENABLED], &mod);
20362306a36Sopenharmony_ci	ethnl_update_bool(&cfg.pmac_enabled, tb[ETHTOOL_A_MM_PMAC_ENABLED],
20462306a36Sopenharmony_ci			  &mod);
20562306a36Sopenharmony_ci	ethnl_update_u32(&cfg.tx_min_frag_size,
20662306a36Sopenharmony_ci			 tb[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE], &mod);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (!mod)
20962306a36Sopenharmony_ci		return 0;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if (cfg.verify_time > state.max_verify_time) {
21262306a36Sopenharmony_ci		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MM_VERIFY_TIME],
21362306a36Sopenharmony_ci				    "verifyTime exceeds device maximum");
21462306a36Sopenharmony_ci		return -ERANGE;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (cfg.verify_enabled && !cfg.tx_enabled) {
21862306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Verification requires TX enabled");
21962306a36Sopenharmony_ci		return -EINVAL;
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (cfg.tx_enabled && !cfg.pmac_enabled) {
22362306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "TX enabled requires pMAC enabled");
22462306a36Sopenharmony_ci		return -EINVAL;
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	ret = dev->ethtool_ops->set_mm(dev, &cfg, extack);
22862306a36Sopenharmony_ci	return ret < 0 ? ret : 1;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_mm_request_ops = {
23262306a36Sopenharmony_ci	.request_cmd		= ETHTOOL_MSG_MM_GET,
23362306a36Sopenharmony_ci	.reply_cmd		= ETHTOOL_MSG_MM_GET_REPLY,
23462306a36Sopenharmony_ci	.hdr_attr		= ETHTOOL_A_MM_HEADER,
23562306a36Sopenharmony_ci	.req_info_size		= sizeof(struct mm_req_info),
23662306a36Sopenharmony_ci	.reply_data_size	= sizeof(struct mm_reply_data),
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	.prepare_data		= mm_prepare_data,
23962306a36Sopenharmony_ci	.reply_size		= mm_reply_size,
24062306a36Sopenharmony_ci	.fill_reply		= mm_fill_reply,
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	.set_validate		= ethnl_set_mm_validate,
24362306a36Sopenharmony_ci	.set			= ethnl_set_mm,
24462306a36Sopenharmony_ci	.set_ntf_cmd		= ETHTOOL_MSG_MM_NTF,
24562306a36Sopenharmony_ci};
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci/* Returns whether a given device supports the MAC merge layer
24862306a36Sopenharmony_ci * (has an eMAC and a pMAC). Must be called under rtnl_lock() and
24962306a36Sopenharmony_ci * ethnl_ops_begin().
25062306a36Sopenharmony_ci */
25162306a36Sopenharmony_cibool __ethtool_dev_mm_supported(struct net_device *dev)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	const struct ethtool_ops *ops = dev->ethtool_ops;
25462306a36Sopenharmony_ci	struct ethtool_mm_state state = {};
25562306a36Sopenharmony_ci	int ret = -EOPNOTSUPP;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (ops && ops->get_mm)
25862306a36Sopenharmony_ci		ret = ops->get_mm(dev, &state);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return !ret;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cibool ethtool_dev_mm_supported(struct net_device *dev)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	const struct ethtool_ops *ops = dev->ethtool_ops;
26662306a36Sopenharmony_ci	bool supported;
26762306a36Sopenharmony_ci	int ret;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	ASSERT_RTNL();
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (!ops)
27262306a36Sopenharmony_ci		return false;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	ret = ethnl_ops_begin(dev);
27562306a36Sopenharmony_ci	if (ret < 0)
27662306a36Sopenharmony_ci		return false;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	supported = __ethtool_dev_mm_supported(dev);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	ethnl_ops_complete(dev);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	return supported;
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ethtool_dev_mm_supported);
285