Merge tag 'v5.9' into next
[platform/kernel/linux-rpi.git] / net / ethtool / cabletest.c
1 // SPDX-License-Identifier: GPL-2.0-only
2
3 #include <linux/phy.h>
4 #include <linux/ethtool_netlink.h>
5 #include "netlink.h"
6 #include "common.h"
7
8 /* 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)
13
14 static const struct nla_policy
15 cable_test_act_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = {
16         [ETHTOOL_A_CABLE_TEST_UNSPEC]           = { .type = NLA_REJECT },
17         [ETHTOOL_A_CABLE_TEST_HEADER]           = { .type = NLA_NESTED },
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 nlattr *tb[ETHTOOL_A_CABLE_TEST_MAX + 1];
60         struct ethnl_req_info req_info = {};
61         const struct ethtool_phy_ops *ops;
62         struct net_device *dev;
63         int ret;
64
65         ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
66                           ETHTOOL_A_CABLE_TEST_MAX,
67                           cable_test_act_policy, info->extack);
68         if (ret < 0)
69                 return ret;
70
71         ret = ethnl_parse_header_dev_get(&req_info,
72                                          tb[ETHTOOL_A_CABLE_TEST_HEADER],
73                                          genl_info_net(info), info->extack,
74                                          true);
75         if (ret < 0)
76                 return ret;
77
78         dev = req_info.dev;
79         if (!dev->phydev) {
80                 ret = -EOPNOTSUPP;
81                 goto out_dev_put;
82         }
83
84         rtnl_lock();
85         ops = ethtool_phy_ops;
86         if (!ops || !ops->start_cable_test) {
87                 ret = -EOPNOTSUPP;
88                 goto out_rtnl;
89         }
90
91         ret = ethnl_ops_begin(dev);
92         if (ret < 0)
93                 goto out_rtnl;
94
95         ret = ops->start_cable_test(dev->phydev, info->extack);
96
97         ethnl_ops_complete(dev);
98
99         if (!ret)
100                 ethnl_cable_test_started(dev->phydev,
101                                          ETHTOOL_MSG_CABLE_TEST_NTF);
102
103 out_rtnl:
104         rtnl_unlock();
105 out_dev_put:
106         dev_put(dev);
107         return ret;
108 }
109
110 int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
111 {
112         int err = -ENOMEM;
113
114         /* One TDR sample occupies 20 bytes. For a 150 meter cable,
115          * with four pairs, around 12K is needed.
116          */
117         phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
118         if (!phydev->skb)
119                 goto out;
120
121         phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
122         if (!phydev->ehdr) {
123                 err = -EMSGSIZE;
124                 goto out;
125         }
126
127         err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
128                                       ETHTOOL_A_CABLE_TEST_NTF_HEADER);
129         if (err)
130                 goto out;
131
132         err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
133                          ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
134         if (err)
135                 goto out;
136
137         phydev->nest = nla_nest_start(phydev->skb,
138                                       ETHTOOL_A_CABLE_TEST_NTF_NEST);
139         if (!phydev->nest) {
140                 err = -EMSGSIZE;
141                 goto out;
142         }
143
144         return 0;
145
146 out:
147         nlmsg_free(phydev->skb);
148         phydev->skb = NULL;
149         return err;
150 }
151 EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
152
153 void ethnl_cable_test_free(struct phy_device *phydev)
154 {
155         nlmsg_free(phydev->skb);
156         phydev->skb = NULL;
157 }
158 EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
159
160 void ethnl_cable_test_finished(struct phy_device *phydev)
161 {
162         nla_nest_end(phydev->skb, phydev->nest);
163
164         genlmsg_end(phydev->skb, phydev->ehdr);
165
166         ethnl_multicast(phydev->skb, phydev->attached_dev);
167 }
168 EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
169
170 int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
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
184         nla_nest_end(phydev->skb, nest);
185         return 0;
186
187 err:
188         nla_nest_cancel(phydev->skb, nest);
189         return ret;
190 }
191 EXPORT_SYMBOL_GPL(ethnl_cable_test_result);
192
193 int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
194 {
195         struct nlattr *nest;
196         int ret = -EMSGSIZE;
197
198         nest = nla_nest_start(phydev->skb,
199                               ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
200         if (!nest)
201                 return -EMSGSIZE;
202
203         if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
204                 goto err;
205         if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
206                 goto err;
207
208         nla_nest_end(phydev->skb, nest);
209         return 0;
210
211 err:
212         nla_nest_cancel(phydev->skb, nest);
213         return ret;
214 }
215 EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length);
216
217 struct cable_test_tdr_req_info {
218         struct ethnl_req_info           base;
219 };
220
221 static const struct nla_policy
222 cable_test_tdr_act_cfg_policy[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1] = {
223         [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]    = { .type = NLA_U32 },
224         [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]     = { .type = NLA_U32 },
225         [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]     = { .type = NLA_U32 },
226         [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]     = { .type = NLA_U8 },
227 };
228
229 static const struct nla_policy
230 cable_test_tdr_act_policy[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1] = {
231         [ETHTOOL_A_CABLE_TEST_TDR_UNSPEC]       = { .type = NLA_REJECT },
232         [ETHTOOL_A_CABLE_TEST_TDR_HEADER]       = { .type = NLA_NESTED },
233         [ETHTOOL_A_CABLE_TEST_TDR_CFG]          = { .type = NLA_NESTED },
234 };
235
236 /* CABLE_TEST_TDR_ACT */
237 static int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest,
238                                         struct genl_info *info,
239                                         struct phy_tdr_config *cfg)
240 {
241         struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1];
242         int ret;
243
244         cfg->first = 100;
245         cfg->step = 100;
246         cfg->last = MAX_CABLE_LENGTH_CM;
247         cfg->pair = PHY_PAIR_ALL;
248
249         if (!nest)
250                 return 0;
251
252         ret = nla_parse_nested(tb, ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, nest,
253                                cable_test_tdr_act_cfg_policy, info->extack);
254         if (ret < 0)
255                 return ret;
256
257         if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST])
258                 cfg->first = nla_get_u32(
259                         tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]);
260
261         if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
262                 cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
263
264         if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
265                 cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
266
267         if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) {
268                 cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]);
269                 if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) {
270                         NL_SET_ERR_MSG_ATTR(
271                                 info->extack,
272                                 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
273                                 "invalid pair parameter");
274                         return -EINVAL;
275                 }
276         }
277
278         if (cfg->first > MAX_CABLE_LENGTH_CM) {
279                 NL_SET_ERR_MSG_ATTR(info->extack,
280                                     tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST],
281                                     "invalid first parameter");
282                 return -EINVAL;
283         }
284
285         if (cfg->last > MAX_CABLE_LENGTH_CM) {
286                 NL_SET_ERR_MSG_ATTR(info->extack,
287                                     tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST],
288                                     "invalid last parameter");
289                 return -EINVAL;
290         }
291
292         if (cfg->first > cfg->last) {
293                 NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
294                 return -EINVAL;
295         }
296
297         if (!cfg->step) {
298                 NL_SET_ERR_MSG_ATTR(info->extack,
299                                     tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
300                                     "invalid step parameter");
301                 return -EINVAL;
302         }
303
304         if (cfg->step > (cfg->last - cfg->first)) {
305                 NL_SET_ERR_MSG_ATTR(info->extack,
306                                     tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
307                                     "step parameter too big");
308                 return -EINVAL;
309         }
310
311         return 0;
312 }
313
314 int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
315 {
316         struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1];
317         struct ethnl_req_info req_info = {};
318         const struct ethtool_phy_ops *ops;
319         struct phy_tdr_config cfg;
320         struct net_device *dev;
321         int ret;
322
323         ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
324                           ETHTOOL_A_CABLE_TEST_TDR_MAX,
325                           cable_test_tdr_act_policy, info->extack);
326         if (ret < 0)
327                 return ret;
328
329         ret = ethnl_parse_header_dev_get(&req_info,
330                                          tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
331                                          genl_info_net(info), info->extack,
332                                          true);
333         if (ret < 0)
334                 return ret;
335
336         dev = req_info.dev;
337         if (!dev->phydev) {
338                 ret = -EOPNOTSUPP;
339                 goto out_dev_put;
340         }
341
342         ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
343                                            info, &cfg);
344         if (ret)
345                 goto out_dev_put;
346
347         rtnl_lock();
348         ops = ethtool_phy_ops;
349         if (!ops || !ops->start_cable_test_tdr) {
350                 ret = -EOPNOTSUPP;
351                 goto out_rtnl;
352         }
353
354         ret = ethnl_ops_begin(dev);
355         if (ret < 0)
356                 goto out_rtnl;
357
358         ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
359
360         ethnl_ops_complete(dev);
361
362         if (!ret)
363                 ethnl_cable_test_started(dev->phydev,
364                                          ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
365
366 out_rtnl:
367         rtnl_unlock();
368 out_dev_put:
369         dev_put(dev);
370         return ret;
371 }
372  
373 int ethnl_cable_test_amplitude(struct phy_device *phydev,
374                                u8 pair, s16 mV)
375 {
376         struct nlattr *nest;
377         int ret = -EMSGSIZE;
378
379         nest = nla_nest_start(phydev->skb,
380                               ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
381         if (!nest)
382                 return -EMSGSIZE;
383
384         if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
385                 goto err;
386         if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
387                 goto err;
388
389         nla_nest_end(phydev->skb, nest);
390         return 0;
391
392 err:
393         nla_nest_cancel(phydev->skb, nest);
394         return ret;
395 }
396 EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
397
398 int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
399 {
400         struct nlattr *nest;
401         int ret = -EMSGSIZE;
402
403         nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
404         if (!nest)
405                 return -EMSGSIZE;
406
407         if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
408                 goto err;
409
410         nla_nest_end(phydev->skb, nest);
411         return 0;
412
413 err:
414         nla_nest_cancel(phydev->skb, nest);
415         return ret;
416 }
417 EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
418
419 int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
420                           u32 step)
421 {
422         struct nlattr *nest;
423         int ret = -EMSGSIZE;
424
425         nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
426         if (!nest)
427                 return -EMSGSIZE;
428
429         if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
430                         first))
431                 goto err;
432
433         if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
434                 goto err;
435
436         if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
437                 goto err;
438
439         nla_nest_end(phydev->skb, nest);
440         return 0;
441
442 err:
443         nla_nest_cancel(phydev->skb, nest);
444         return ret;
445 }
446 EXPORT_SYMBOL_GPL(ethnl_cable_test_step);