Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/net/ethtool/cabletest.c
26282 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
3
#include <linux/phy.h>
4
#include <linux/ethtool_netlink.h>
5
#include <net/netdev_lock.h>
6
#include "netlink.h"
7
#include "common.h"
8
9
/* 802.3 standard allows 100 meters for BaseT cables. However longer
10
* cables might work, depending on the quality of the cables and the
11
* PHY. So allow testing for up to 150 meters.
12
*/
13
#define MAX_CABLE_LENGTH_CM (150 * 100)
14
15
const struct nla_policy ethnl_cable_test_act_policy[] = {
16
[ETHTOOL_A_CABLE_TEST_HEADER] =
17
NLA_POLICY_NESTED(ethnl_header_policy_phy),
18
};
19
20
static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
21
{
22
struct sk_buff *skb;
23
int err = -ENOMEM;
24
void *ehdr;
25
26
skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
27
if (!skb)
28
goto out;
29
30
ehdr = ethnl_bcastmsg_put(skb, cmd);
31
if (!ehdr) {
32
err = -EMSGSIZE;
33
goto out;
34
}
35
36
err = ethnl_fill_reply_header(skb, phydev->attached_dev,
37
ETHTOOL_A_CABLE_TEST_NTF_HEADER);
38
if (err)
39
goto out;
40
41
err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
42
ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
43
if (err)
44
goto out;
45
46
genlmsg_end(skb, ehdr);
47
48
return ethnl_multicast(skb, phydev->attached_dev);
49
50
out:
51
nlmsg_free(skb);
52
phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
53
54
return err;
55
}
56
57
int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
58
{
59
struct ethnl_req_info req_info = {};
60
const struct ethtool_phy_ops *ops;
61
struct nlattr **tb = info->attrs;
62
struct phy_device *phydev;
63
struct net_device *dev;
64
int ret;
65
66
ret = ethnl_parse_header_dev_get(&req_info,
67
tb[ETHTOOL_A_CABLE_TEST_HEADER],
68
genl_info_net(info), info->extack,
69
true);
70
if (ret < 0)
71
return ret;
72
73
dev = req_info.dev;
74
75
rtnl_lock();
76
netdev_lock_ops(dev);
77
phydev = ethnl_req_get_phydev(&req_info, tb,
78
ETHTOOL_A_CABLE_TEST_HEADER,
79
info->extack);
80
if (IS_ERR_OR_NULL(phydev)) {
81
ret = -EOPNOTSUPP;
82
goto out_unlock;
83
}
84
85
ops = ethtool_phy_ops;
86
if (!ops || !ops->start_cable_test) {
87
ret = -EOPNOTSUPP;
88
goto out_unlock;
89
}
90
91
ret = ethnl_ops_begin(dev);
92
if (ret < 0)
93
goto out_unlock;
94
95
ret = ops->start_cable_test(phydev, info->extack);
96
97
ethnl_ops_complete(dev);
98
99
if (!ret)
100
ethnl_cable_test_started(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
101
102
out_unlock:
103
netdev_unlock_ops(dev);
104
rtnl_unlock();
105
ethnl_parse_header_dev_put(&req_info);
106
return ret;
107
}
108
109
int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
110
{
111
int err = -ENOMEM;
112
113
/* One TDR sample occupies 20 bytes. For a 150 meter cable,
114
* with four pairs, around 12K is needed.
115
*/
116
phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
117
if (!phydev->skb)
118
goto out;
119
120
phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
121
if (!phydev->ehdr) {
122
err = -EMSGSIZE;
123
goto out;
124
}
125
126
err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
127
ETHTOOL_A_CABLE_TEST_NTF_HEADER);
128
if (err)
129
goto out;
130
131
err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
132
ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
133
if (err)
134
goto out;
135
136
phydev->nest = nla_nest_start(phydev->skb,
137
ETHTOOL_A_CABLE_TEST_NTF_NEST);
138
if (!phydev->nest) {
139
err = -EMSGSIZE;
140
goto out;
141
}
142
143
return 0;
144
145
out:
146
nlmsg_free(phydev->skb);
147
phydev->skb = NULL;
148
return err;
149
}
150
EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
151
152
void ethnl_cable_test_free(struct phy_device *phydev)
153
{
154
nlmsg_free(phydev->skb);
155
phydev->skb = NULL;
156
}
157
EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
158
159
void ethnl_cable_test_finished(struct phy_device *phydev)
160
{
161
nla_nest_end(phydev->skb, phydev->nest);
162
163
genlmsg_end(phydev->skb, phydev->ehdr);
164
165
ethnl_multicast(phydev->skb, phydev->attached_dev);
166
}
167
EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
168
169
int ethnl_cable_test_result_with_src(struct phy_device *phydev, u8 pair,
170
u8 result, u32 src)
171
{
172
struct nlattr *nest;
173
int ret = -EMSGSIZE;
174
175
nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
176
if (!nest)
177
return -EMSGSIZE;
178
179
if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
180
goto err;
181
if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
182
goto err;
183
if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
184
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_RESULT_SRC, src))
185
goto err;
186
}
187
188
nla_nest_end(phydev->skb, nest);
189
return 0;
190
191
err:
192
nla_nest_cancel(phydev->skb, nest);
193
return ret;
194
}
195
EXPORT_SYMBOL_GPL(ethnl_cable_test_result_with_src);
196
197
int ethnl_cable_test_fault_length_with_src(struct phy_device *phydev, u8 pair,
198
u32 cm, u32 src)
199
{
200
struct nlattr *nest;
201
int ret = -EMSGSIZE;
202
203
nest = nla_nest_start(phydev->skb,
204
ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
205
if (!nest)
206
return -EMSGSIZE;
207
208
if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
209
goto err;
210
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
211
goto err;
212
if (src != ETHTOOL_A_CABLE_INF_SRC_UNSPEC) {
213
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_SRC,
214
src))
215
goto err;
216
}
217
218
nla_nest_end(phydev->skb, nest);
219
return 0;
220
221
err:
222
nla_nest_cancel(phydev->skb, nest);
223
return ret;
224
}
225
EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length_with_src);
226
227
static const struct nla_policy cable_test_tdr_act_cfg_policy[] = {
228
[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 },
229
[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .type = NLA_U32 },
230
[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .type = NLA_U32 },
231
[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .type = NLA_U8 },
232
};
233
234
const struct nla_policy ethnl_cable_test_tdr_act_policy[] = {
235
[ETHTOOL_A_CABLE_TEST_TDR_HEADER] =
236
NLA_POLICY_NESTED(ethnl_header_policy_phy),
237
[ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED },
238
};
239
240
/* CABLE_TEST_TDR_ACT */
241
static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
242
struct genl_info *info,
243
struct phy_tdr_config *cfg)
244
{
245
struct nlattr *tb[ARRAY_SIZE(cable_test_tdr_act_cfg_policy)];
246
int ret;
247
248
cfg->first = 100;
249
cfg->step = 100;
250
cfg->last = MAX_CABLE_LENGTH_CM;
251
cfg->pair = PHY_PAIR_ALL;
252
253
if (!nest)
254
return 0;
255
256
ret = nla_parse_nested(tb,
257
ARRAY_SIZE(cable_test_tdr_act_cfg_policy) - 1,
258
nest, cable_test_tdr_act_cfg_policy,
259
info->extack);
260
if (ret < 0)
261
return ret;
262
263
if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
264
cfg->first = nla_get_u32(
265
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
266
267
if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
268
cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
269
270
if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
271
cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
272
273
if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
274
cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
275
if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
276
NL_SET_ERR_MSG_ATTR(
277
info->extack,
278
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
279
"invalid pair parameter");
280
return -EINVAL;
281
}
282
}
283
284
if (cfg->first > MAX_CABLE_LENGTH_CM) {
285
NL_SET_ERR_MSG_ATTR(info->extack,
286
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
287
"invalid first parameter");
288
return -EINVAL;
289
}
290
291
if (cfg->last > MAX_CABLE_LENGTH_CM) {
292
NL_SET_ERR_MSG_ATTR(info->extack,
293
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
294
"invalid last parameter");
295
return -EINVAL;
296
}
297
298
if (cfg->first > cfg->last) {
299
NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
300
return -EINVAL;
301
}
302
303
if (!cfg->step) {
304
NL_SET_ERR_MSG_ATTR(info->extack,
305
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
306
"invalid step parameter");
307
return -EINVAL;
308
}
309
310
if (cfg->step > (cfg->last - cfg->first)) {
311
NL_SET_ERR_MSG_ATTR(info->extack,
312
tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
313
"step parameter too big");
314
return -EINVAL;
315
}
316
317
return 0;
318
}
319
320
int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
321
{
322
struct ethnl_req_info req_info = {};
323
const struct ethtool_phy_ops *ops;
324
struct nlattr **tb = info->attrs;
325
struct phy_device *phydev;
326
struct phy_tdr_config cfg;
327
struct net_device *dev;
328
int ret;
329
330
ret = ethnl_parse_header_dev_get(&req_info,
331
tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
332
genl_info_net(info), info->extack,
333
true);
334
if (ret < 0)
335
return ret;
336
337
dev = req_info.dev;
338
339
ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
340
info, &cfg);
341
if (ret)
342
goto out_dev_put;
343
344
rtnl_lock();
345
netdev_lock_ops(dev);
346
phydev = ethnl_req_get_phydev(&req_info, tb,
347
ETHTOOL_A_CABLE_TEST_TDR_HEADER,
348
info->extack);
349
if (IS_ERR_OR_NULL(phydev)) {
350
ret = -EOPNOTSUPP;
351
goto out_unlock;
352
}
353
354
ops = ethtool_phy_ops;
355
if (!ops || !ops->start_cable_test_tdr) {
356
ret = -EOPNOTSUPP;
357
goto out_unlock;
358
}
359
360
ret = ethnl_ops_begin(dev);
361
if (ret < 0)
362
goto out_unlock;
363
364
ret = ops->start_cable_test_tdr(phydev, info->extack, &cfg);
365
366
ethnl_ops_complete(dev);
367
368
if (!ret)
369
ethnl_cable_test_started(phydev,
370
ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
371
372
out_unlock:
373
netdev_unlock_ops(dev);
374
rtnl_unlock();
375
out_dev_put:
376
ethnl_parse_header_dev_put(&req_info);
377
return ret;
378
}
379
380
int ethnl_cable_test_amplitude(struct phy_device *phydev,
381
u8 pair, s16 mV)
382
{
383
struct nlattr *nest;
384
int ret = -EMSGSIZE;
385
386
nest = nla_nest_start(phydev->skb,
387
ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
388
if (!nest)
389
return -EMSGSIZE;
390
391
if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
392
goto err;
393
if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
394
goto err;
395
396
nla_nest_end(phydev->skb, nest);
397
return 0;
398
399
err:
400
nla_nest_cancel(phydev->skb, nest);
401
return ret;
402
}
403
EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
404
405
int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
406
{
407
struct nlattr *nest;
408
int ret = -EMSGSIZE;
409
410
nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
411
if (!nest)
412
return -EMSGSIZE;
413
414
if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
415
goto err;
416
417
nla_nest_end(phydev->skb, nest);
418
return 0;
419
420
err:
421
nla_nest_cancel(phydev->skb, nest);
422
return ret;
423
}
424
EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
425
426
int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
427
u32 step)
428
{
429
struct nlattr *nest;
430
int ret = -EMSGSIZE;
431
432
nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
433
if (!nest)
434
return -EMSGSIZE;
435
436
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
437
first))
438
goto err;
439
440
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
441
goto err;
442
443
if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
444
goto err;
445
446
nla_nest_end(phydev->skb, nest);
447
return 0;
448
449
err:
450
nla_nest_cancel(phydev->skb, nest);
451
return ret;
452
}
453
EXPORT_SYMBOL_GPL(ethnl_cable_test_step);
454
455