blob: 6f3328be65926709ddfd58460a1906ad019d0098 [file] [log] [blame]
Andrew Lunn11ca3c422020-05-10 21:12:33 +02001// SPDX-License-Identifier: GPL-2.0-only
2
3#include <linux/phy.h>
Andrew Lunn1dd3f212020-05-10 21:12:36 +02004#include <linux/ethtool_netlink.h>
Andrew Lunn11ca3c422020-05-10 21:12:33 +02005#include "netlink.h"
6#include "common.h"
7
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +02008/* 802.3 standard allows 100 meters for BaseT cables. However longer
9 * cables might work, depending on the quality of the cables and the
10 * PHY. So allow testing for up to 150 meters.
11 */
12#define MAX_CABLE_LENGTH_CM (150 * 100)
Andrew Lunn11ca3c422020-05-10 21:12:33 +020013
Jakub Kicinskiff419af2020-10-05 15:07:35 -070014const struct nla_policy ethnl_cable_test_act_policy[] = {
Andrew Lunn11ca3c422020-05-10 21:12:33 +020015 [ETHTOOL_A_CABLE_TEST_HEADER] = { .type = NLA_NESTED },
16};
17
Andrew Lunn1a644de2020-05-27 00:21:38 +020018static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
Andrew Lunn9896a452020-05-10 21:12:40 +020019{
20 struct sk_buff *skb;
21 int err = -ENOMEM;
22 void *ehdr;
23
24 skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
25 if (!skb)
26 goto out;
27
Andrew Lunn1a644de2020-05-27 00:21:38 +020028 ehdr = ethnl_bcastmsg_put(skb, cmd);
Andrew Lunn9896a452020-05-10 21:12:40 +020029 if (!ehdr) {
30 err = -EMSGSIZE;
31 goto out;
32 }
33
34 err = ethnl_fill_reply_header(skb, phydev->attached_dev,
35 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
36 if (err)
37 goto out;
38
39 err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
40 ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
41 if (err)
42 goto out;
43
44 genlmsg_end(skb, ehdr);
45
46 return ethnl_multicast(skb, phydev->attached_dev);
47
48out:
49 nlmsg_free(skb);
50 phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
51
52 return err;
53}
54
Andrew Lunn11ca3c422020-05-10 21:12:33 +020055int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
56{
Andrew Lunn11ca3c422020-05-10 21:12:33 +020057 struct ethnl_req_info req_info = {};
Florian Fainellif3631ab2020-07-05 21:27:58 -070058 const struct ethtool_phy_ops *ops;
Jakub Kicinski5028588b2020-10-05 15:07:34 -070059 struct nlattr **tb = info->attrs;
Andrew Lunn11ca3c422020-05-10 21:12:33 +020060 struct net_device *dev;
61 int ret;
62
Andrew Lunn11ca3c422020-05-10 21:12:33 +020063 ret = ethnl_parse_header_dev_get(&req_info,
64 tb[ETHTOOL_A_CABLE_TEST_HEADER],
65 genl_info_net(info), info->extack,
66 true);
67 if (ret < 0)
68 return ret;
69
70 dev = req_info.dev;
71 if (!dev->phydev) {
72 ret = -EOPNOTSUPP;
73 goto out_dev_put;
74 }
75
76 rtnl_lock();
Florian Fainellif3631ab2020-07-05 21:27:58 -070077 ops = ethtool_phy_ops;
78 if (!ops || !ops->start_cable_test) {
79 ret = -EOPNOTSUPP;
80 goto out_rtnl;
81 }
82
Andrew Lunn11ca3c422020-05-10 21:12:33 +020083 ret = ethnl_ops_begin(dev);
84 if (ret < 0)
85 goto out_rtnl;
86
Florian Fainellif3631ab2020-07-05 21:27:58 -070087 ret = ops->start_cable_test(dev->phydev, info->extack);
Andrew Lunn11ca3c422020-05-10 21:12:33 +020088
89 ethnl_ops_complete(dev);
Andrew Lunn9896a452020-05-10 21:12:40 +020090
91 if (!ret)
Andrew Lunn1a644de2020-05-27 00:21:38 +020092 ethnl_cable_test_started(dev->phydev,
93 ETHTOOL_MSG_CABLE_TEST_NTF);
Andrew Lunn9896a452020-05-10 21:12:40 +020094
Andrew Lunn11ca3c422020-05-10 21:12:33 +020095out_rtnl:
96 rtnl_unlock();
97out_dev_put:
98 dev_put(dev);
99 return ret;
100}
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200101
Andrew Lunn1a644de2020-05-27 00:21:38 +0200102int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200103{
104 int err = -ENOMEM;
105
Andrew Lunn6b4a0fc2020-05-27 00:21:39 +0200106 /* One TDR sample occupies 20 bytes. For a 150 meter cable,
107 * with four pairs, around 12K is needed.
108 */
109 phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200110 if (!phydev->skb)
111 goto out;
112
Andrew Lunn1a644de2020-05-27 00:21:38 +0200113 phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200114 if (!phydev->ehdr) {
115 err = -EMSGSIZE;
116 goto out;
117 }
118
119 err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
120 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
121 if (err)
122 goto out;
123
124 err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
125 ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
126 if (err)
127 goto out;
128
129 phydev->nest = nla_nest_start(phydev->skb,
130 ETHTOOL_A_CABLE_TEST_NTF_NEST);
Andrew Lunn1e2dc142020-05-10 21:12:37 +0200131 if (!phydev->nest) {
132 err = -EMSGSIZE;
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200133 goto out;
Andrew Lunn1e2dc142020-05-10 21:12:37 +0200134 }
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200135
136 return 0;
137
138out:
139 nlmsg_free(phydev->skb);
Andrew Lunn1e2dc142020-05-10 21:12:37 +0200140 phydev->skb = NULL;
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200141 return err;
142}
143EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
144
145void ethnl_cable_test_free(struct phy_device *phydev)
146{
147 nlmsg_free(phydev->skb);
Andrew Lunn1e2dc142020-05-10 21:12:37 +0200148 phydev->skb = NULL;
Andrew Lunn1dd3f212020-05-10 21:12:36 +0200149}
150EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
151
152void ethnl_cable_test_finished(struct phy_device *phydev)
153{
154 nla_nest_end(phydev->skb, phydev->nest);
155
156 genlmsg_end(phydev->skb, phydev->ehdr);
157
158 ethnl_multicast(phydev->skb, phydev->attached_dev);
159}
160EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
Andrew Lunn1e2dc142020-05-10 21:12:37 +0200161
162int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
163{
164 struct nlattr *nest;
165 int ret = -EMSGSIZE;
166
167 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
168 if (!nest)
169 return -EMSGSIZE;
170
171 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
172 goto err;
173 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
174 goto err;
175
176 nla_nest_end(phydev->skb, nest);
177 return 0;
178
179err:
180 nla_nest_cancel(phydev->skb, nest);
181 return ret;
182}
183EXPORT_SYMBOL_GPL(ethnl_cable_test_result);
184
185int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
186{
187 struct nlattr *nest;
188 int ret = -EMSGSIZE;
189
190 nest = nla_nest_start(phydev->skb,
191 ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
192 if (!nest)
193 return -EMSGSIZE;
194
195 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
196 goto err;
197 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
198 goto err;
199
200 nla_nest_end(phydev->skb, nest);
201 return 0;
202
203err:
204 nla_nest_cancel(phydev->skb, nest);
205 return ret;
206}
207EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length);
Andrew Lunn1a644de2020-05-27 00:21:38 +0200208
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200209struct cable_test_tdr_req_info {
210 struct ethnl_req_info base;
211};
212
Jakub Kicinskiff419af2020-10-05 15:07:35 -0700213static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200214 [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },
215 [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .type = NLA_U32 },
216 [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .type = NLA_U32 },
217 [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .type = NLA_U8 },
218};
219
Jakub Kicinskiff419af2020-10-05 15:07:35 -0700220const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
Andrew Lunn1a644de2020-05-27 00:21:38 +0200221 [ETHTOOL_A_CABLE_TEST_TDR_HEADER] = { .type = NLA_NESTED },
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200222 [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
Andrew Lunn1a644de2020-05-27 00:21:38 +0200223};
224
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200225/* CABLE_TEST_TDR_ACT */
Andrew Lunnfd551992020-05-28 23:43:24 +0200226static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
227 struct genl_info *info,
228 struct phy_tdr_config *cfg)
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200229{
Jakub Kicinskiff419af2020-10-05 15:07:35 -0700230 struct nlattr *tb[ARRAY_SIZE(cable_test_tdr_act_cfg_policy)];
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200231 int ret;
232
Andrew Lunn4b973f42020-06-24 03:25:45 +0200233 cfg->first = 100;
234 cfg->step = 100;
235 cfg->last = MAX_CABLE_LENGTH_CM;
236 cfg->pair = PHY_PAIR_ALL;
237
238 if (!nest)
239 return 0;
240
Jakub Kicinskiff419af2020-10-05 15:07:35 -0700241 ret = nla_parse_nested(tb,
242 ARRAY_SIZE(cable_test_tdr_act_cfg_policy) - 1,
243 nest, cable_test_tdr_act_cfg_policy,
244 info->extack);
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200245 if (ret < 0)
246 return ret;
247
248 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
249 cfg->first = nla_get_u32(
250 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
Andrew Lunn4b973f42020-06-24 03:25:45 +0200251
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200252 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
253 cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200254
255 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
256 cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200257
258 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
259 cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
260 if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
261 NL_SET_ERR_MSG_ATTR(
262 info->extack,
263 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
264 "invalid pair parameter");
265 return -EINVAL;
266 }
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200267 }
268
269 if (cfg->first > MAX_CABLE_LENGTH_CM) {
270 NL_SET_ERR_MSG_ATTR(info->extack,
271 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
272 "invalid first parameter");
273 return -EINVAL;
274 }
275
276 if (cfg->last > MAX_CABLE_LENGTH_CM) {
277 NL_SET_ERR_MSG_ATTR(info->extack,
278 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
279 "invalid last parameter");
280 return -EINVAL;
281 }
282
283 if (cfg->first > cfg->last) {
284 NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
285 return -EINVAL;
286 }
287
288 if (!cfg->step) {
289 NL_SET_ERR_MSG_ATTR(info->extack,
290 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
291 "invalid step parameter");
292 return -EINVAL;
293 }
294
295 if (cfg->step > (cfg->last - cfg->first)) {
296 NL_SET_ERR_MSG_ATTR(info->extack,
297 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
298 "step parameter too big");
299 return -EINVAL;
300 }
301
302 return 0;
303}
304
Andrew Lunn1a644de2020-05-27 00:21:38 +0200305int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
306{
Andrew Lunn1a644de2020-05-27 00:21:38 +0200307 struct ethnl_req_info req_info = {};
Florian Fainellif3631ab2020-07-05 21:27:58 -0700308 const struct ethtool_phy_ops *ops;
Jakub Kicinski5028588b2020-10-05 15:07:34 -0700309 struct nlattr **tb = info->attrs;
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200310 struct phy_tdr_config cfg;
Andrew Lunn1a644de2020-05-27 00:21:38 +0200311 struct net_device *dev;
312 int ret;
313
Andrew Lunn1a644de2020-05-27 00:21:38 +0200314 ret = ethnl_parse_header_dev_get(&req_info,
315 tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
316 genl_info_net(info), info->extack,
317 true);
318 if (ret < 0)
319 return ret;
320
321 dev = req_info.dev;
322 if (!dev->phydev) {
323 ret = -EOPNOTSUPP;
324 goto out_dev_put;
325 }
326
Andrew Lunnf2bc8ad2020-05-27 00:21:41 +0200327 ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
328 info, &cfg);
329 if (ret)
330 goto out_dev_put;
331
Andrew Lunn1a644de2020-05-27 00:21:38 +0200332 rtnl_lock();
Florian Fainellif3631ab2020-07-05 21:27:58 -0700333 ops = ethtool_phy_ops;
334 if (!ops || !ops->start_cable_test_tdr) {
335 ret = -EOPNOTSUPP;
336 goto out_rtnl;
337 }
338
Andrew Lunn1a644de2020-05-27 00:21:38 +0200339 ret = ethnl_ops_begin(dev);
340 if (ret < 0)
341 goto out_rtnl;
342
Florian Fainellif3631ab2020-07-05 21:27:58 -0700343 ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
Andrew Lunn1a644de2020-05-27 00:21:38 +0200344
345 ethnl_ops_complete(dev);
346
347 if (!ret)
348 ethnl_cable_test_started(dev->phydev,
349 ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
350
351out_rtnl:
352 rtnl_unlock();
353out_dev_put:
354 dev_put(dev);
355 return ret;
356}
Andrew Lunn6b4a0fc2020-05-27 00:21:39 +0200357
358int ethnl_cable_test_amplitude(struct phy_device *phydev,
359 u8 pair, s16 mV)
360{
361 struct nlattr *nest;
362 int ret = -EMSGSIZE;
363
364 nest = nla_nest_start(phydev->skb,
365 ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
366 if (!nest)
367 return -EMSGSIZE;
368
369 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
370 goto err;
371 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
372 goto err;
373
374 nla_nest_end(phydev->skb, nest);
375 return 0;
376
377err:
378 nla_nest_cancel(phydev->skb, nest);
379 return ret;
380}
381EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
382
383int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
384{
385 struct nlattr *nest;
386 int ret = -EMSGSIZE;
387
388 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
389 if (!nest)
390 return -EMSGSIZE;
391
392 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
393 goto err;
394
395 nla_nest_end(phydev->skb, nest);
396 return 0;
397
398err:
399 nla_nest_cancel(phydev->skb, nest);
400 return ret;
401}
402EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
403
404int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
405 u32 step)
406{
407 struct nlattr *nest;
408 int ret = -EMSGSIZE;
409
410 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
411 if (!nest)
412 return -EMSGSIZE;
413
414 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
415 first))
416 goto err;
417
418 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
419 goto err;
420
421 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
422 goto err;
423
424 nla_nest_end(phydev->skb, nest);
425 return 0;
426
427err:
428 nla_nest_cancel(phydev->skb, nest);
429 return ret;
430}
431EXPORT_SYMBOL_GPL(ethnl_cable_test_step);