xref: /openbmc/linux/net/ethtool/mm.c (revision dc0b98a1)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2022-2023 NXP
4  */
5 #include "common.h"
6 #include "netlink.h"
7 
8 struct mm_req_info {
9 	struct ethnl_req_info		base;
10 };
11 
12 struct mm_reply_data {
13 	struct ethnl_reply_data		base;
14 	struct ethtool_mm_state		state;
15 	struct ethtool_mm_stats		stats;
16 };
17 
18 #define MM_REPDATA(__reply_base) \
19 	container_of(__reply_base, struct mm_reply_data, base)
20 
21 #define ETHTOOL_MM_STAT_CNT \
22 	(__ETHTOOL_A_MM_STAT_CNT - (ETHTOOL_A_MM_STAT_PAD + 1))
23 
24 const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1] = {
25 	[ETHTOOL_A_MM_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_stats),
26 };
27 
28 static int mm_prepare_data(const struct ethnl_req_info *req_base,
29 			   struct ethnl_reply_data *reply_base,
30 			   struct genl_info *info)
31 {
32 	struct mm_reply_data *data = MM_REPDATA(reply_base);
33 	struct net_device *dev = reply_base->dev;
34 	const struct ethtool_ops *ops;
35 	int ret;
36 
37 	ops = dev->ethtool_ops;
38 
39 	if (!ops->get_mm)
40 		return -EOPNOTSUPP;
41 
42 	ethtool_stats_init((u64 *)&data->stats,
43 			   sizeof(data->stats) / sizeof(u64));
44 
45 	ret = ethnl_ops_begin(dev);
46 	if (ret < 0)
47 		return ret;
48 
49 	ret = ops->get_mm(dev, &data->state);
50 	if (ret)
51 		goto out_complete;
52 
53 	if (ops->get_mm_stats && (req_base->flags & ETHTOOL_FLAG_STATS))
54 		ops->get_mm_stats(dev, &data->stats);
55 
56 out_complete:
57 	ethnl_ops_complete(dev);
58 
59 	return 0;
60 }
61 
62 static int mm_reply_size(const struct ethnl_req_info *req_base,
63 			 const struct ethnl_reply_data *reply_base)
64 {
65 	int len = 0;
66 
67 	len += nla_total_size(sizeof(u8)); /* _MM_PMAC_ENABLED */
68 	len += nla_total_size(sizeof(u8)); /* _MM_TX_ENABLED */
69 	len += nla_total_size(sizeof(u8)); /* _MM_TX_ACTIVE */
70 	len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_ENABLED */
71 	len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_STATUS */
72 	len += nla_total_size(sizeof(u32)); /* _MM_VERIFY_TIME */
73 	len += nla_total_size(sizeof(u32)); /* _MM_MAX_VERIFY_TIME */
74 	len += nla_total_size(sizeof(u32)); /* _MM_TX_MIN_FRAG_SIZE */
75 	len += nla_total_size(sizeof(u32)); /* _MM_RX_MIN_FRAG_SIZE */
76 
77 	if (req_base->flags & ETHTOOL_FLAG_STATS)
78 		len += nla_total_size(0) + /* _MM_STATS */
79 		       nla_total_size_64bit(sizeof(u64)) * ETHTOOL_MM_STAT_CNT;
80 
81 	return len;
82 }
83 
84 static int mm_put_stat(struct sk_buff *skb, u64 val, u16 attrtype)
85 {
86 	if (val == ETHTOOL_STAT_NOT_SET)
87 		return 0;
88 	if (nla_put_u64_64bit(skb, attrtype, val, ETHTOOL_A_MM_STAT_PAD))
89 		return -EMSGSIZE;
90 	return 0;
91 }
92 
93 static int mm_put_stats(struct sk_buff *skb,
94 			const struct ethtool_mm_stats *stats)
95 {
96 	struct nlattr *nest;
97 
98 	nest = nla_nest_start(skb, ETHTOOL_A_MM_STATS);
99 	if (!nest)
100 		return -EMSGSIZE;
101 
102 	if (mm_put_stat(skb, stats->MACMergeFrameAssErrorCount,
103 			ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS) ||
104 	    mm_put_stat(skb, stats->MACMergeFrameSmdErrorCount,
105 			ETHTOOL_A_MM_STAT_SMD_ERRORS) ||
106 	    mm_put_stat(skb, stats->MACMergeFrameAssOkCount,
107 			ETHTOOL_A_MM_STAT_REASSEMBLY_OK) ||
108 	    mm_put_stat(skb, stats->MACMergeFragCountRx,
109 			ETHTOOL_A_MM_STAT_RX_FRAG_COUNT) ||
110 	    mm_put_stat(skb, stats->MACMergeFragCountTx,
111 			ETHTOOL_A_MM_STAT_TX_FRAG_COUNT) ||
112 	    mm_put_stat(skb, stats->MACMergeHoldCount,
113 			ETHTOOL_A_MM_STAT_HOLD_COUNT))
114 		goto err_cancel;
115 
116 	nla_nest_end(skb, nest);
117 	return 0;
118 
119 err_cancel:
120 	nla_nest_cancel(skb, nest);
121 	return -EMSGSIZE;
122 }
123 
124 static int mm_fill_reply(struct sk_buff *skb,
125 			 const struct ethnl_req_info *req_base,
126 			 const struct ethnl_reply_data *reply_base)
127 {
128 	const struct mm_reply_data *data = MM_REPDATA(reply_base);
129 	const struct ethtool_mm_state *state = &data->state;
130 
131 	if (nla_put_u8(skb, ETHTOOL_A_MM_TX_ENABLED, state->tx_enabled) ||
132 	    nla_put_u8(skb, ETHTOOL_A_MM_TX_ACTIVE, state->tx_active) ||
133 	    nla_put_u8(skb, ETHTOOL_A_MM_PMAC_ENABLED, state->pmac_enabled) ||
134 	    nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_ENABLED, state->verify_enabled) ||
135 	    nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_STATUS, state->verify_status) ||
136 	    nla_put_u32(skb, ETHTOOL_A_MM_VERIFY_TIME, state->verify_time) ||
137 	    nla_put_u32(skb, ETHTOOL_A_MM_MAX_VERIFY_TIME, state->max_verify_time) ||
138 	    nla_put_u32(skb, ETHTOOL_A_MM_TX_MIN_FRAG_SIZE, state->tx_min_frag_size) ||
139 	    nla_put_u32(skb, ETHTOOL_A_MM_RX_MIN_FRAG_SIZE, state->rx_min_frag_size))
140 		return -EMSGSIZE;
141 
142 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
143 	    mm_put_stats(skb, &data->stats))
144 		return -EMSGSIZE;
145 
146 	return 0;
147 }
148 
149 const struct ethnl_request_ops ethnl_mm_request_ops = {
150 	.request_cmd		= ETHTOOL_MSG_MM_GET,
151 	.reply_cmd		= ETHTOOL_MSG_MM_GET_REPLY,
152 	.hdr_attr		= ETHTOOL_A_MM_HEADER,
153 	.req_info_size		= sizeof(struct mm_req_info),
154 	.reply_data_size	= sizeof(struct mm_reply_data),
155 
156 	.prepare_data		= mm_prepare_data,
157 	.reply_size		= mm_reply_size,
158 	.fill_reply		= mm_fill_reply,
159 };
160 
161 const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1] = {
162 	[ETHTOOL_A_MM_HEADER]		= NLA_POLICY_NESTED(ethnl_header_policy),
163 	[ETHTOOL_A_MM_VERIFY_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
164 	[ETHTOOL_A_MM_VERIFY_TIME]	= NLA_POLICY_RANGE(NLA_U32, 1, 128),
165 	[ETHTOOL_A_MM_TX_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
166 	[ETHTOOL_A_MM_PMAC_ENABLED]	= NLA_POLICY_MAX(NLA_U8, 1),
167 	[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE]	= NLA_POLICY_RANGE(NLA_U32, 60, 252),
168 };
169 
170 static void mm_state_to_cfg(const struct ethtool_mm_state *state,
171 			    struct ethtool_mm_cfg *cfg)
172 {
173 	/* We could also compare state->verify_status against
174 	 * ETHTOOL_MM_VERIFY_STATUS_DISABLED, but state->verify_enabled
175 	 * is more like an administrative state which should be seen in
176 	 * ETHTOOL_MSG_MM_GET replies. For example, a port with verification
177 	 * disabled might be in the ETHTOOL_MM_VERIFY_STATUS_INITIAL
178 	 * if it's down.
179 	 */
180 	cfg->verify_enabled = state->verify_enabled;
181 	cfg->verify_time = state->verify_time;
182 	cfg->tx_enabled = state->tx_enabled;
183 	cfg->pmac_enabled = state->pmac_enabled;
184 	cfg->tx_min_frag_size = state->tx_min_frag_size;
185 }
186 
187 int ethnl_set_mm(struct sk_buff *skb, struct genl_info *info)
188 {
189 	struct netlink_ext_ack *extack = info->extack;
190 	struct ethnl_req_info req_info = {};
191 	struct ethtool_mm_state state = {};
192 	struct nlattr **tb = info->attrs;
193 	struct ethtool_mm_cfg cfg = {};
194 	const struct ethtool_ops *ops;
195 	struct net_device *dev;
196 	bool mod = false;
197 	int ret;
198 
199 	ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_MM_HEADER],
200 					 genl_info_net(info), extack, true);
201 	if (ret)
202 		return ret;
203 
204 	dev = req_info.dev;
205 	ops = dev->ethtool_ops;
206 
207 	if (!ops->get_mm || !ops->set_mm) {
208 		ret = -EOPNOTSUPP;
209 		goto out_dev_put;
210 	}
211 
212 	rtnl_lock();
213 	ret = ethnl_ops_begin(dev);
214 	if (ret < 0)
215 		goto out_rtnl_unlock;
216 
217 	ret = ops->get_mm(dev, &state);
218 	if (ret)
219 		goto out_complete;
220 
221 	mm_state_to_cfg(&state, &cfg);
222 
223 	ethnl_update_bool(&cfg.verify_enabled, tb[ETHTOOL_A_MM_VERIFY_ENABLED],
224 			  &mod);
225 	ethnl_update_u32(&cfg.verify_time, tb[ETHTOOL_A_MM_VERIFY_TIME], &mod);
226 	ethnl_update_bool(&cfg.tx_enabled, tb[ETHTOOL_A_MM_TX_ENABLED], &mod);
227 	ethnl_update_bool(&cfg.pmac_enabled, tb[ETHTOOL_A_MM_PMAC_ENABLED],
228 			    &mod);
229 	ethnl_update_u32(&cfg.tx_min_frag_size,
230 			 tb[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE], &mod);
231 
232 	if (!mod)
233 		goto out_complete;
234 
235 	if (cfg.verify_time > state.max_verify_time) {
236 		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MM_VERIFY_TIME],
237 				    "verifyTime exceeds device maximum");
238 		ret = -ERANGE;
239 		goto out_complete;
240 	}
241 
242 	ret = ops->set_mm(dev, &cfg, extack);
243 	if (ret)
244 		goto out_complete;
245 
246 	ethtool_notify(dev, ETHTOOL_MSG_MM_NTF, NULL);
247 
248 out_complete:
249 	ethnl_ops_complete(dev);
250 out_rtnl_unlock:
251 	rtnl_unlock();
252 out_dev_put:
253 	ethnl_parse_header_dev_put(&req_info);
254 	return ret;
255 }
256 
257 /* Returns whether a given device supports the MAC merge layer
258  * (has an eMAC and a pMAC). Must be called under rtnl_lock() and
259  * ethnl_ops_begin().
260  */
261 bool __ethtool_dev_mm_supported(struct net_device *dev)
262 {
263 	const struct ethtool_ops *ops = dev->ethtool_ops;
264 	struct ethtool_mm_state state = {};
265 	int ret = -EOPNOTSUPP;
266 
267 	if (ops && ops->get_mm)
268 		ret = ops->get_mm(dev, &state);
269 
270 	return !!ret;
271 }
272