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