1 // SPDX-License-Identifier: GPL-2.0-only
4 #include <linux/ethtool_netlink.h>
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.
12 #define MAX_CABLE_LENGTH_CM (150 * 100)
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 },
20 static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd)
26 skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
30 ehdr = ethnl_bcastmsg_put(skb, cmd);
36 err = ethnl_fill_reply_header(skb, phydev->attached_dev,
37 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
41 err = nla_put_u8(skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
42 ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED);
46 genlmsg_end(skb, ehdr);
48 return ethnl_multicast(skb, phydev->attached_dev);
52 phydev_err(phydev, "%s: Error %pe\n", __func__, ERR_PTR(err));
57 int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
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;
65 ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
66 ETHTOOL_A_CABLE_TEST_MAX,
67 cable_test_act_policy, info->extack);
71 ret = ethnl_parse_header_dev_get(&req_info,
72 tb[ETHTOOL_A_CABLE_TEST_HEADER],
73 genl_info_net(info), info->extack,
85 ops = ethtool_phy_ops;
86 if (!ops || !ops->start_cable_test) {
91 ret = ethnl_ops_begin(dev);
95 ret = ops->start_cable_test(dev->phydev, info->extack);
97 ethnl_ops_complete(dev);
100 ethnl_cable_test_started(dev->phydev,
101 ETHTOOL_MSG_CABLE_TEST_NTF);
110 int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
114 /* One TDR sample occupies 20 bytes. For a 150 meter cable,
115 * with four pairs, around 12K is needed.
117 phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL);
121 phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd);
127 err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
128 ETHTOOL_A_CABLE_TEST_NTF_HEADER);
132 err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
133 ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
137 phydev->nest = nla_nest_start(phydev->skb,
138 ETHTOOL_A_CABLE_TEST_NTF_NEST);
147 nlmsg_free(phydev->skb);
151 EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
153 void ethnl_cable_test_free(struct phy_device *phydev)
155 nlmsg_free(phydev->skb);
158 EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
160 void ethnl_cable_test_finished(struct phy_device *phydev)
162 nla_nest_end(phydev->skb, phydev->nest);
164 genlmsg_end(phydev->skb, phydev->ehdr);
166 ethnl_multicast(phydev->skb, phydev->attached_dev);
168 EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);
170 int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result)
175 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_NEST_RESULT);
179 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_PAIR, pair))
181 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_RESULT_CODE, result))
184 nla_nest_end(phydev->skb, nest);
188 nla_nest_cancel(phydev->skb, nest);
191 EXPORT_SYMBOL_GPL(ethnl_cable_test_result);
193 int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
198 nest = nla_nest_start(phydev->skb,
199 ETHTOOL_A_CABLE_NEST_FAULT_LENGTH);
203 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR, pair))
205 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_FAULT_LENGTH_CM, cm))
208 nla_nest_end(phydev->skb, nest);
212 nla_nest_cancel(phydev->skb, nest);
215 EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length);
217 struct cable_test_tdr_req_info {
218 struct ethnl_req_info base;
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 },
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 },
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)
241 struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1];
246 cfg->last = MAX_CABLE_LENGTH_CM;
247 cfg->pair = PHY_PAIR_ALL;
252 ret = nla_parse_nested(tb, ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, nest,
253 cable_test_tdr_act_cfg_policy, info->extack);
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]);
261 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST])
262 cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]);
264 if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP])
265 cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]);
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) {
272 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR],
273 "invalid pair parameter");
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");
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");
292 if (cfg->first > cfg->last) {
293 NL_SET_ERR_MSG(info->extack, "invalid first/last parameter");
298 NL_SET_ERR_MSG_ATTR(info->extack,
299 tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP],
300 "invalid step parameter");
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");
314 int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
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;
323 ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
324 ETHTOOL_A_CABLE_TEST_TDR_MAX,
325 cable_test_tdr_act_policy, info->extack);
329 ret = ethnl_parse_header_dev_get(&req_info,
330 tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER],
331 genl_info_net(info), info->extack,
342 ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG],
348 ops = ethtool_phy_ops;
349 if (!ops || !ops->start_cable_test_tdr) {
354 ret = ethnl_ops_begin(dev);
358 ret = ops->start_cable_test_tdr(dev->phydev, info->extack, &cfg);
360 ethnl_ops_complete(dev);
363 ethnl_cable_test_started(dev->phydev,
364 ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
373 int ethnl_cable_test_amplitude(struct phy_device *phydev,
379 nest = nla_nest_start(phydev->skb,
380 ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE);
384 if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair))
386 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV))
389 nla_nest_end(phydev->skb, nest);
393 nla_nest_cancel(phydev->skb, nest);
396 EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude);
398 int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV)
403 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE);
407 if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV))
410 nla_nest_end(phydev->skb, nest);
414 nla_nest_cancel(phydev->skb, nest);
417 EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse);
419 int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last,
425 nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP);
429 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE,
433 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last))
436 if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step))
439 nla_nest_end(phydev->skb, nest);
443 nla_nest_cancel(phydev->skb, nest);
446 EXPORT_SYMBOL_GPL(ethnl_cable_test_step);