xref: /linux/net/ethtool/tsconfig.c (revision d6f6d7123355388f2f41c1b6c108bfdba18b0cfc)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/net_tstamp.h>
4 #include <linux/ptp_clock_kernel.h>
5 
6 #include "bitset.h"
7 #include "common.h"
8 #include "netlink.h"
9 #include "ts.h"
10 #include "../core/dev.h"
11 
12 struct tsconfig_req_info {
13 	struct ethnl_req_info base;
14 };
15 
16 struct tsconfig_reply_data {
17 	struct ethnl_reply_data		base;
18 	struct hwtstamp_provider_desc	hwprov_desc;
19 	struct {
20 		u32 tx_type;
21 		u32 rx_filter;
22 		u32 flags;
23 	} hwtst_config;
24 };
25 
26 #define TSCONFIG_REPDATA(__reply_base) \
27 	container_of(__reply_base, struct tsconfig_reply_data, base)
28 
29 const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEADER + 1] = {
30 	[ETHTOOL_A_TSCONFIG_HEADER]		=
31 		NLA_POLICY_NESTED(ethnl_header_policy),
32 };
33 
34 static int tsconfig_prepare_data(const struct ethnl_req_info *req_base,
35 				 struct ethnl_reply_data *reply_base,
36 				 const struct genl_info *info)
37 {
38 	struct tsconfig_reply_data *data = TSCONFIG_REPDATA(reply_base);
39 	struct hwtstamp_provider *hwprov = NULL;
40 	struct net_device *dev = reply_base->dev;
41 	struct kernel_hwtstamp_config cfg = {};
42 	int ret;
43 
44 	if (!dev->netdev_ops->ndo_hwtstamp_get)
45 		return -EOPNOTSUPP;
46 
47 	ret = ethnl_ops_begin(dev);
48 	if (ret < 0)
49 		return ret;
50 
51 	ret = dev_get_hwtstamp_phylib(dev, &cfg);
52 	if (ret)
53 		goto out;
54 
55 	data->hwtst_config.tx_type = BIT(cfg.tx_type);
56 	data->hwtst_config.rx_filter = BIT(cfg.rx_filter);
57 	data->hwtst_config.flags = cfg.flags;
58 
59 	data->hwprov_desc.index = -1;
60 	hwprov = rtnl_dereference(dev->hwprov);
61 	if (hwprov) {
62 		data->hwprov_desc.index = hwprov->desc.index;
63 		data->hwprov_desc.qualifier = hwprov->desc.qualifier;
64 	} else {
65 		struct kernel_ethtool_ts_info ts_info = {};
66 
67 		ts_info.phc_index = -1;
68 		ret = __ethtool_get_ts_info(dev, &ts_info);
69 		if (ret)
70 			goto out;
71 
72 		if (ts_info.phc_index == -1) {
73 			ret = -ENODEV;
74 			goto out;
75 		}
76 
77 		data->hwprov_desc.index = ts_info.phc_index;
78 		data->hwprov_desc.qualifier = ts_info.phc_qualifier;
79 	}
80 
81 out:
82 	ethnl_ops_complete(dev);
83 	return ret;
84 }
85 
86 static int tsconfig_reply_size(const struct ethnl_req_info *req_base,
87 			       const struct ethnl_reply_data *reply_base)
88 {
89 	const struct tsconfig_reply_data *data = TSCONFIG_REPDATA(reply_base);
90 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
91 	int len = 0;
92 	int ret;
93 
94 	BUILD_BUG_ON(__HWTSTAMP_TX_CNT > 32);
95 	BUILD_BUG_ON(__HWTSTAMP_FILTER_CNT > 32);
96 	BUILD_BUG_ON(__HWTSTAMP_FLAG_CNT > 32);
97 
98 	if (data->hwtst_config.flags) {
99 		ret = ethnl_bitset32_size(&data->hwtst_config.flags,
100 					  NULL, __HWTSTAMP_FLAG_CNT,
101 					  ts_flags_names, compact);
102 		if (ret < 0)
103 			return ret;
104 		len += ret;	/* _TSCONFIG_HWTSTAMP_FLAGS */
105 	}
106 
107 	if (data->hwtst_config.tx_type) {
108 		ret = ethnl_bitset32_size(&data->hwtst_config.tx_type,
109 					  NULL, __HWTSTAMP_TX_CNT,
110 					  ts_tx_type_names, compact);
111 		if (ret < 0)
112 			return ret;
113 		len += ret;	/* _TSCONFIG_TX_TYPES */
114 	}
115 	if (data->hwtst_config.rx_filter) {
116 		ret = ethnl_bitset32_size(&data->hwtst_config.rx_filter,
117 					  NULL, __HWTSTAMP_FILTER_CNT,
118 					  ts_rx_filter_names, compact);
119 		if (ret < 0)
120 			return ret;
121 		len += ret;	/* _TSCONFIG_RX_FILTERS */
122 	}
123 
124 	if (data->hwprov_desc.index >= 0)
125 		/* _TSCONFIG_HWTSTAMP_PROVIDER */
126 		len += nla_total_size(0) +
127 		       2 * nla_total_size(sizeof(u32));
128 
129 	return len;
130 }
131 
132 static int tsconfig_fill_reply(struct sk_buff *skb,
133 			       const struct ethnl_req_info *req_base,
134 			       const struct ethnl_reply_data *reply_base)
135 {
136 	const struct tsconfig_reply_data *data = TSCONFIG_REPDATA(reply_base);
137 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
138 	int ret;
139 
140 	if (data->hwtst_config.flags) {
141 		ret = ethnl_put_bitset32(skb, ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS,
142 					 &data->hwtst_config.flags, NULL,
143 					 __HWTSTAMP_FLAG_CNT,
144 					 ts_flags_names, compact);
145 		if (ret < 0)
146 			return ret;
147 	}
148 
149 	if (data->hwtst_config.tx_type) {
150 		ret = ethnl_put_bitset32(skb, ETHTOOL_A_TSCONFIG_TX_TYPES,
151 					 &data->hwtst_config.tx_type, NULL,
152 					 __HWTSTAMP_TX_CNT,
153 					 ts_tx_type_names, compact);
154 		if (ret < 0)
155 			return ret;
156 	}
157 
158 	if (data->hwtst_config.rx_filter) {
159 		ret = ethnl_put_bitset32(skb, ETHTOOL_A_TSCONFIG_RX_FILTERS,
160 					 &data->hwtst_config.rx_filter,
161 					 NULL, __HWTSTAMP_FILTER_CNT,
162 					 ts_rx_filter_names, compact);
163 		if (ret < 0)
164 			return ret;
165 	}
166 
167 	if (data->hwprov_desc.index >= 0) {
168 		struct nlattr *nest;
169 
170 		nest = nla_nest_start(skb, ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER);
171 		if (!nest)
172 			return -EMSGSIZE;
173 
174 		if (nla_put_u32(skb, ETHTOOL_A_TS_HWTSTAMP_PROVIDER_INDEX,
175 				data->hwprov_desc.index) ||
176 		    nla_put_u32(skb,
177 				ETHTOOL_A_TS_HWTSTAMP_PROVIDER_QUALIFIER,
178 				data->hwprov_desc.qualifier)) {
179 			nla_nest_cancel(skb, nest);
180 			return -EMSGSIZE;
181 		}
182 
183 		nla_nest_end(skb, nest);
184 	}
185 	return 0;
186 }
187 
188 /* TSCONFIG_SET */
189 const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1] = {
190 	[ETHTOOL_A_TSCONFIG_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
191 	[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER] =
192 		NLA_POLICY_NESTED(ethnl_ts_hwtst_prov_policy),
193 	[ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS] = { .type = NLA_NESTED },
194 	[ETHTOOL_A_TSCONFIG_RX_FILTERS] = { .type = NLA_NESTED },
195 	[ETHTOOL_A_TSCONFIG_TX_TYPES] = { .type = NLA_NESTED },
196 };
197 
198 static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info)
199 {
200 	struct tsconfig_reply_data *reply_data;
201 	struct tsconfig_req_info *req_info;
202 	struct sk_buff *rskb;
203 	void *reply_payload;
204 	int reply_len = 0;
205 	int ret;
206 
207 	req_info = kzalloc_obj(*req_info);
208 	if (!req_info)
209 		return -ENOMEM;
210 	reply_data = kmalloc_obj(*reply_data);
211 	if (!reply_data) {
212 		kfree(req_info);
213 		return -ENOMEM;
214 	}
215 
216 	ASSERT_RTNL();
217 	reply_data->base.dev = dev;
218 	ret = tsconfig_prepare_data(&req_info->base, &reply_data->base, info);
219 	if (ret < 0)
220 		goto err_cleanup;
221 
222 	ret = tsconfig_reply_size(&req_info->base, &reply_data->base);
223 	if (ret < 0)
224 		goto err_cleanup;
225 
226 	reply_len = ret + ethnl_reply_header_size();
227 	rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_TSCONFIG_SET_REPLY,
228 				ETHTOOL_A_TSCONFIG_HEADER, info, &reply_payload);
229 	if (!rskb) {
230 		ret = -ENOMEM;
231 		goto err_cleanup;
232 	}
233 
234 	ret = tsconfig_fill_reply(rskb, &req_info->base, &reply_data->base);
235 	if (ret < 0)
236 		goto err_free_msg;
237 
238 	genlmsg_end(rskb, reply_payload);
239 	ret = genlmsg_reply(rskb, info);
240 	rskb = NULL;
241 
242 err_free_msg:
243 	nlmsg_free(rskb);
244 err_cleanup:
245 	kfree(reply_data);
246 	kfree(req_info);
247 	return ret;
248 }
249 
250 static int ethnl_set_tsconfig_validate(struct ethnl_req_info *req_base,
251 				       struct genl_info *info)
252 {
253 	const struct net_device_ops *ops = req_base->dev->netdev_ops;
254 
255 	if (!ops->ndo_hwtstamp_set || !ops->ndo_hwtstamp_get)
256 		return -EOPNOTSUPP;
257 
258 	return 1;
259 }
260 
261 static struct hwtstamp_provider *
262 tsconfig_set_hwprov_from_desc(struct net_device *dev,
263 			      struct genl_info *info,
264 			      struct hwtstamp_provider_desc *hwprov_desc)
265 {
266 	struct kernel_ethtool_ts_info ts_info;
267 	struct hwtstamp_provider *hwprov;
268 	struct nlattr **tb = info->attrs;
269 	struct phy_device *phy = NULL;
270 	enum hwtstamp_source source;
271 	int ret;
272 
273 	ret = ethtool_net_get_ts_info_by_phc(dev, &ts_info, hwprov_desc);
274 	if (!ret) {
275 		/* Found */
276 		source = HWTSTAMP_SOURCE_NETDEV;
277 	} else {
278 		phy = ethtool_phy_get_ts_info_by_phc(dev, &ts_info, hwprov_desc);
279 		if (IS_ERR(phy)) {
280 			if (PTR_ERR(phy) == -ENODEV)
281 				NL_SET_ERR_MSG_ATTR(info->extack,
282 						    tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER],
283 						    "phc not in this net device topology");
284 			return ERR_CAST(phy);
285 		}
286 
287 		source = HWTSTAMP_SOURCE_PHYLIB;
288 	}
289 
290 	hwprov = kzalloc_obj(*hwprov);
291 	if (!hwprov)
292 		return ERR_PTR(-ENOMEM);
293 
294 	hwprov->desc.index = hwprov_desc->index;
295 	hwprov->desc.qualifier = hwprov_desc->qualifier;
296 	hwprov->source = source;
297 	hwprov->phydev = phy;
298 
299 	return hwprov;
300 }
301 
302 static int ethnl_set_tsconfig(struct ethnl_req_info *req_base,
303 			      struct genl_info *info)
304 {
305 	struct kernel_hwtstamp_config hwtst_config = {0};
306 	bool hwprov_mod = false, config_mod = false;
307 	struct hwtstamp_provider *hwprov = NULL;
308 	struct net_device *dev = req_base->dev;
309 	struct nlattr **tb = info->attrs;
310 	int ret;
311 
312 	if (!netif_device_present(dev))
313 		return -ENODEV;
314 
315 	if (tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER]) {
316 		struct hwtstamp_provider_desc __hwprov_desc = {.index = -1};
317 		struct hwtstamp_provider *__hwprov;
318 
319 		__hwprov = rtnl_dereference(dev->hwprov);
320 		if (__hwprov) {
321 			__hwprov_desc.index = __hwprov->desc.index;
322 			__hwprov_desc.qualifier = __hwprov->desc.qualifier;
323 		}
324 
325 		ret = ts_parse_hwtst_provider(tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER],
326 					      &__hwprov_desc, info->extack,
327 					      &hwprov_mod);
328 		if (ret < 0)
329 			return ret;
330 
331 		if (hwprov_mod) {
332 			hwprov = tsconfig_set_hwprov_from_desc(dev, info,
333 							       &__hwprov_desc);
334 			if (IS_ERR(hwprov))
335 				return PTR_ERR(hwprov);
336 		}
337 	}
338 
339 	/* Get current hwtstamp config if we are not changing the
340 	 * hwtstamp source. It will be zeroed in the other case.
341 	 */
342 	if (!hwprov_mod) {
343 		ret = dev_get_hwtstamp_phylib(dev, &hwtst_config);
344 		if (ret < 0 && ret != -EOPNOTSUPP)
345 			goto err_free_hwprov;
346 	}
347 
348 	/* Get the hwtstamp config from netlink */
349 	if (tb[ETHTOOL_A_TSCONFIG_TX_TYPES]) {
350 		u32 req_tx_type;
351 
352 		req_tx_type = BIT(hwtst_config.tx_type);
353 		ret = ethnl_update_bitset32(&req_tx_type,
354 					    __HWTSTAMP_TX_CNT,
355 					    tb[ETHTOOL_A_TSCONFIG_TX_TYPES],
356 					    ts_tx_type_names, info->extack,
357 					    &config_mod);
358 		if (ret < 0)
359 			goto err_free_hwprov;
360 
361 		/* Select only one tx type at a time */
362 		if (ffs(req_tx_type) != fls(req_tx_type)) {
363 			ret = -EINVAL;
364 			goto err_free_hwprov;
365 		}
366 
367 		hwtst_config.tx_type = ffs(req_tx_type) - 1;
368 	}
369 
370 	if (tb[ETHTOOL_A_TSCONFIG_RX_FILTERS]) {
371 		u32 req_rx_filter;
372 
373 		req_rx_filter = BIT(hwtst_config.rx_filter);
374 		ret = ethnl_update_bitset32(&req_rx_filter,
375 					    __HWTSTAMP_FILTER_CNT,
376 					    tb[ETHTOOL_A_TSCONFIG_RX_FILTERS],
377 					    ts_rx_filter_names, info->extack,
378 					    &config_mod);
379 		if (ret < 0)
380 			goto err_free_hwprov;
381 
382 		/* Select only one rx filter at a time */
383 		if (ffs(req_rx_filter) != fls(req_rx_filter)) {
384 			ret = -EINVAL;
385 			goto err_free_hwprov;
386 		}
387 
388 		hwtst_config.rx_filter = ffs(req_rx_filter) - 1;
389 	}
390 
391 	if (tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS]) {
392 		ret = ethnl_update_bitset32(&hwtst_config.flags,
393 					    __HWTSTAMP_FLAG_CNT,
394 					    tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS],
395 					    ts_flags_names, info->extack,
396 					    &config_mod);
397 		if (ret < 0)
398 			goto err_free_hwprov;
399 	}
400 
401 	ret = net_hwtstamp_validate(&hwtst_config);
402 	if (ret)
403 		goto err_free_hwprov;
404 
405 	if (hwprov_mod) {
406 		struct kernel_hwtstamp_config zero_config = {0};
407 		struct hwtstamp_provider *__hwprov;
408 
409 		/* Disable current time stamping if we try to enable
410 		 * another one
411 		 */
412 		ret = dev_set_hwtstamp_phylib(dev, &zero_config, info->extack);
413 		if (ret < 0)
414 			goto err_free_hwprov;
415 
416 		/* Change the selected hwtstamp source */
417 		__hwprov = rcu_replace_pointer_rtnl(dev->hwprov, hwprov);
418 		if (__hwprov)
419 			kfree_rcu(__hwprov, rcu_head);
420 	}
421 
422 	if (config_mod) {
423 		ret = dev_set_hwtstamp_phylib(dev, &hwtst_config,
424 					      info->extack);
425 		if (ret < 0)
426 			return ret;
427 	}
428 
429 	ret = tsconfig_send_reply(dev, info);
430 	if (ret && ret != -EOPNOTSUPP) {
431 		NL_SET_ERR_MSG(info->extack,
432 			       "error while reading the new configuration set");
433 		return ret;
434 	}
435 
436 	/* tsconfig has no notification */
437 	return 0;
438 
439 err_free_hwprov:
440 	kfree(hwprov);
441 
442 	return ret;
443 }
444 
445 const struct ethnl_request_ops ethnl_tsconfig_request_ops = {
446 	.request_cmd		= ETHTOOL_MSG_TSCONFIG_GET,
447 	.reply_cmd		= ETHTOOL_MSG_TSCONFIG_GET_REPLY,
448 	.hdr_attr		= ETHTOOL_A_TSCONFIG_HEADER,
449 	.req_info_size		= sizeof(struct tsconfig_req_info),
450 	.reply_data_size	= sizeof(struct tsconfig_reply_data),
451 
452 	.prepare_data		= tsconfig_prepare_data,
453 	.reply_size		= tsconfig_reply_size,
454 	.fill_reply		= tsconfig_fill_reply,
455 
456 	.set_validate		= ethnl_set_tsconfig_validate,
457 	.set			= ethnl_set_tsconfig,
458 };
459