net: ethtool: Add infrastructure for reporting cable test results
authorAndrew Lunn <andrew@lunn.ch>
Sun, 10 May 2020 19:12:36 +0000 (21:12 +0200)
committerJakub Kicinski <kuba@kernel.org>
Sun, 10 May 2020 19:28:41 +0000 (12:28 -0700)
Provide infrastructure for PHY drivers to report the cable test
results.  A netlink skb is associated to the phydev. Helpers will be
added which can add results to this skb. Once the test has finished
the results are sent to user space.

When netlink ethtool is not part of the kernel configuration stubs are
provided. It is also impossible to trigger a cable test, so the error
code returned by the alloc function is of no consequence.

v2:
Include the status complete in the netlink notification message

v4:
Replace -EINVAL with -EMSGSIZE

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Michal Kubecek <mkubecek@suse.cz>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/phy/phy.c
include/linux/ethtool_netlink.h
include/linux/phy.h
net/ethtool/cabletest.c

index 9fa61019533f3f77d0ad73b4103b3897131a8cc9..afdc1c2146eeb280faebbeff6e0e44ed60eda733 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/module.h>
 #include <linux/mii.h>
 #include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
 #include <linux/phy.h>
 #include <linux/phy_led_triggers.h>
 #include <linux/sfp.h>
@@ -30,6 +31,9 @@
 #include <linux/io.h>
 #include <linux/uaccess.h>
 #include <linux/atomic.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <net/sock.h>
 
 #define PHY_STATE_TIME HZ
 
@@ -478,6 +482,8 @@ static void phy_abort_cable_test(struct phy_device *phydev)
 {
        int err;
 
+       ethnl_cable_test_finished(phydev);
+
        err = phy_init_hw(phydev);
        if (err)
                phydev_err(phydev, "Error while aborting cable test");
@@ -486,7 +492,7 @@ static void phy_abort_cable_test(struct phy_device *phydev)
 int phy_start_cable_test(struct phy_device *phydev,
                         struct netlink_ext_ack *extack)
 {
-       int err;
+       int err = -ENOMEM;
 
        if (!(phydev->drv &&
              phydev->drv->cable_test_start &&
@@ -512,19 +518,30 @@ int phy_start_cable_test(struct phy_device *phydev,
                goto out;
        }
 
+       err = ethnl_cable_test_alloc(phydev);
+       if (err)
+               goto out;
+
        /* Mark the carrier down until the test is complete */
        phy_link_down(phydev, true);
 
        err = phydev->drv->cable_test_start(phydev);
        if (err) {
                phy_link_up(phydev);
-               goto out;
+               goto out_free;
        }
 
        phydev->state = PHY_CABLETEST;
 
        if (phy_polling_mode(phydev))
                phy_trigger_machine(phydev);
+
+       mutex_unlock(&phydev->lock);
+
+       return 0;
+
+out_free:
+       ethnl_cable_test_free(phydev);
 out:
        mutex_unlock(&phydev->lock);
 
@@ -964,6 +981,7 @@ void phy_state_machine(struct work_struct *work)
                }
 
                if (finished) {
+                       ethnl_cable_test_finished(phydev);
                        needs_aneg = true;
                        phydev->state = PHY_UP;
                }
index d01b77887f822162e4b662ab7342e641d7546f5d..7d763ba22f6f2224d10bbe711ece6f9ee7b26bb7 100644 (file)
@@ -14,4 +14,24 @@ enum ethtool_multicast_groups {
        ETHNL_MCGRP_MONITOR,
 };
 
+struct phy_device;
+
+#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
+int ethnl_cable_test_alloc(struct phy_device *phydev);
+void ethnl_cable_test_free(struct phy_device *phydev);
+void ethnl_cable_test_finished(struct phy_device *phydev);
+#else
+static inline int ethnl_cable_test_alloc(struct phy_device *phydev)
+{
+       return -ENOTSUPP;
+}
+
+static inline void ethnl_cable_test_free(struct phy_device *phydev)
+{
+}
+
+static inline void ethnl_cable_test_finished(struct phy_device *phydev)
+{
+}
+#endif /* IS_ENABLED(ETHTOOL_NETLINK) */
 #endif /* _LINUX_ETHTOOL_NETLINK_H_ */
index f58eee735a4512a0d1af4719d92a3e331c6adc40..169fae4249a911ffc8625900b43452b5111cc693 100644 (file)
@@ -523,6 +523,11 @@ struct phy_device {
        /* For use by PHYs inside the same package that need a shared state. */
        struct phy_package_shared *shared;
 
+       /* Reporting cable test results */
+       struct sk_buff *skb;
+       void *ehdr;
+       struct nlattr *nest;
+
        /* Interrupt and Polling infrastructure */
        struct delayed_work state_queue;
 
index aeb6672a46d0692f9a020b0fce0be84707a0c904..ae8e63647663eb1212e840f3d6530de0915e4fce 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
 #include <linux/phy.h>
+#include <linux/ethtool_netlink.h>
 #include "netlink.h"
 #include "common.h"
 
@@ -52,3 +53,57 @@ out_dev_put:
        dev_put(dev);
        return ret;
 }
+
+int ethnl_cable_test_alloc(struct phy_device *phydev)
+{
+       int err = -ENOMEM;
+
+       phydev->skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!phydev->skb)
+               goto out;
+
+       phydev->ehdr = ethnl_bcastmsg_put(phydev->skb,
+                                         ETHTOOL_MSG_CABLE_TEST_NTF);
+       if (!phydev->ehdr) {
+               err = -EMSGSIZE;
+               goto out;
+       }
+
+       err = ethnl_fill_reply_header(phydev->skb, phydev->attached_dev,
+                                     ETHTOOL_A_CABLE_TEST_NTF_HEADER);
+       if (err)
+               goto out;
+
+       err = nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_TEST_NTF_STATUS,
+                        ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED);
+       if (err)
+               goto out;
+
+       phydev->nest = nla_nest_start(phydev->skb,
+                                     ETHTOOL_A_CABLE_TEST_NTF_NEST);
+       if (!phydev->nest)
+               goto out;
+
+       return 0;
+
+out:
+       nlmsg_free(phydev->skb);
+       return err;
+}
+EXPORT_SYMBOL_GPL(ethnl_cable_test_alloc);
+
+void ethnl_cable_test_free(struct phy_device *phydev)
+{
+       nlmsg_free(phydev->skb);
+}
+EXPORT_SYMBOL_GPL(ethnl_cable_test_free);
+
+void ethnl_cable_test_finished(struct phy_device *phydev)
+{
+       nla_nest_end(phydev->skb, phydev->nest);
+
+       genlmsg_end(phydev->skb, phydev->ehdr);
+
+       ethnl_multicast(phydev->skb, phydev->attached_dev);
+}
+EXPORT_SYMBOL_GPL(ethnl_cable_test_finished);