Add new ethtool netlink calls to trigger the starting of a PHY cable
test.

Signed-off-by: Andrew Lunn <and...@lunn.ch>
---
 drivers/net/phy/Kconfig              |  1 +
 include/uapi/linux/ethtool_netlink.h | 12 +++++
 net/ethtool/actions.c                | 77 ++++++++++++++++++++++++++++
 net/ethtool/netlink.c                |  6 +++
 net/ethtool/netlink.h                |  4 ++
 5 files changed, 100 insertions(+)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index d6299710d634..fc2be56fd2f8 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -208,6 +208,7 @@ menuconfig PHYLIB
        tristate "PHY Device support and infrastructure"
        depends on NETDEVICES
        select MDIO_DEVICE
+       select ETHTOOL_NETLINK
        help
          Ethernet controllers are usually attached to PHY
          devices.  This option provides infrastructure for
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index c6686ebb35b2..e9d0d6fac23b 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -27,6 +27,7 @@ enum {
        ETHNL_CMD_ACT_RESET,
        ETHNL_CMD_GET_RXFLOW,
        ETHNL_CMD_SET_RXFLOW,
+       ETHNL_CMD_ACT_CABLE_TEST,
 
        __ETHNL_CMD_CNT,
        ETHNL_CMD_MAX = (__ETHNL_CMD_CNT - 1)
@@ -106,6 +107,7 @@ enum {
        ETHTOOL_A_EVENT_NEWDEV,                 /* nest - ETHTOOL_A_NEWDEV_* */
        ETHTOOL_A_EVENT_DELDEV,                 /* nest - ETHTOOL_A_DELDEV_* */
        ETHTOOL_A_EVENT_RENAMEDEV,              /* nest - ETHTOOL_A_RENAMEDEV_* 
*/
+       ETHTOOL_A_EVENT_CABLE_TEST,             /* nest - 
ETHTOOL_A_CABLE_TEST_* */
 
        __ETHTOOL_A_EVENT_CNT,
        ETHTOOL_A_EVENT_MAX = (__ETHTOOL_A_EVENT_CNT - 1)
@@ -504,6 +506,16 @@ enum {
        ETHTOOL_A_RXHASHOPT_MAX = (__ETHTOOL_A_RXHASHOPT_CNT - 1)
 };
 
+/* ACT_CABLE_TEST */
+
+enum {
+       ETHTOOL_A_CABLE_TEST_UNSPEC,
+       ETHTOOL_A_CABLE_TEST_DEV,               /* nest - ETHTOOL_A_DEV_* */
+
+       __ETHTOOL_A_CABLE_TEST_CNT,
+       ETHTOOL_A_CABLE_TEST_MAX = (__ETHTOOL_A_CABLE_TEST_CNT - 1)
+};
+
 enum {
        ETHTOOL_A_INDTBL_UNSPEC,
        ETHTOOL_A_INDTBL_BLOCK32,               /* nest - ETH_ITBLK_* */
diff --git a/net/ethtool/actions.c b/net/ethtool/actions.c
index 1fa630cf303f..8a26ae1b2ada 100644
--- a/net/ethtool/actions.c
+++ b/net/ethtool/actions.c
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
 
+#include <linux/phy.h>
 #include "netlink.h"
 #include "common.h"
 #include "bitset.h"
@@ -375,3 +376,79 @@ int ethnl_act_reset(struct sk_buff *skb, struct genl_info 
*info)
                GENL_SET_ERR_MSG(info, "failed to send reply message");
        return ret;
 }
+
+/* ACT_CABLE_TEST */
+
+static const struct
+nla_policy cable_test_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = {
+       [ETHTOOL_A_CABLE_TEST_UNSPEC]   = { .type = NLA_REJECT },
+       [ETHTOOL_A_CABLE_TEST_DEV]              = { .type = NLA_NESTED },
+};
+
+void ethnl_cable_test_notify(struct net_device *dev,
+                            struct netlink_ext_ack *extack, unsigned int cmd,
+                            u32 req_mask, const void *data)
+{
+       struct sk_buff *skb;
+       void *msg_payload;
+       int msg_len;
+       int ret;
+
+       msg_len = dev_ident_size();
+       skb = genlmsg_new(msg_len, GFP_KERNEL);
+       if (!skb)
+               return;
+
+       msg_payload = ethnl_bcastmsg_put(skb, ETHNL_CMD_ACT_CABLE_TEST);
+       if (!msg_payload)
+               goto err_skb;
+
+       ret = ethnl_fill_dev(skb, dev, ETHTOOL_A_CABLE_TEST_DEV);
+       if (ret < 0)
+               goto err_skb;
+
+       genlmsg_end(skb, msg_payload);
+       ethnl_multicast(skb, dev);
+       return;
+
+err_skb:
+       nlmsg_free(skb);
+}
+
+int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
+{
+       struct nlattr *tb[ETHTOOL_A_CABLE_TEST_MAX + 1];
+       struct net_device *dev;
+       int ret;
+
+       ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
+                         ETHTOOL_A_CABLE_TEST_MAX, cable_test_policy,
+                         info->extack);
+       if (ret < 0)
+               return ret;
+
+       dev = ethnl_dev_get(info, tb[ETHTOOL_A_CABLE_TEST_DEV]);
+       if (IS_ERR(dev))
+               return PTR_ERR(dev);
+
+       ret = -EOPNOTSUPP;
+       if (!dev->phydev)
+               goto out_dev;
+
+       rtnl_lock();
+       ret = ethnl_before_ops(dev);
+       if (ret < 0)
+               goto out_rtnl;
+
+       ret = phy_start_cable_test(dev->phydev, info->extack);
+       ethnl_after_ops(dev);
+
+       if (ret == 0)
+               ethtool_notify(dev, NULL, ETHNL_CMD_ACT_CABLE_TEST, 0, NULL);
+
+out_rtnl:
+       rtnl_unlock();
+out_dev:
+       dev_put(dev);
+       return ret;
+}
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 540c92091fe9..894dc81536c9 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -589,6 +589,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] 
= {
        [ETHNL_CMD_ACT_PHYS_ID]         = ethnl_physid_notify,
        [ETHNL_CMD_ACT_RESET]           = ethnl_reset_notify,
        [ETHNL_CMD_SET_RXFLOW]          = ethnl_rxflow_notify,
+       [ETHNL_CMD_ACT_CABLE_TEST]      = ethnl_cable_test_notify,
 };
 
 void ethtool_notify(struct net_device *dev, struct netlink_ext_ack *extack,
@@ -747,6 +748,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
                .flags  = GENL_UNS_ADMIN_PERM,
                .doit   = ethnl_set_rxflow,
        },
+       {
+               .cmd    = ETHNL_CMD_ACT_CABLE_TEST,
+               .flags  = GENL_UNS_ADMIN_PERM,
+               .doit   = ethnl_act_cable_test,
+       },
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index cb7dce82cc7e..4e7b40a8401d 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -269,6 +269,7 @@ int ethnl_set_rxflow(struct sk_buff *skb, struct genl_info 
*info);
 int ethnl_act_nway_rst(struct sk_buff *skb, struct genl_info *info);
 int ethnl_act_phys_id(struct sk_buff *skb, struct genl_info *info);
 int ethnl_act_reset(struct sk_buff *skb, struct genl_info *info);
+int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
 
 /* notify handlers */
 
@@ -281,5 +282,8 @@ void ethnl_reset_notify(struct net_device *dev, struct 
netlink_ext_ack *extack,
                        unsigned int cmd, u32 req_mask, const void *data);
 void ethnl_rxflow_notify(struct net_device *dev, struct netlink_ext_ack 
*extack,
                         unsigned int cmd, u32 req_mask, const void *data);
+void ethnl_cable_test_notify(struct net_device *dev,
+                            struct netlink_ext_ack *extack,
+                            unsigned int cmd, u32 req_mask, const void *data);
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.20.1

Reply via email to