aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/net/phy/phy.c22
-rw-r--r--include/linux/ethtool_netlink.h20
-rw-r--r--include/linux/phy.h5
-rw-r--r--net/ethtool/cabletest.c55
4 files changed, 100 insertions, 2 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 9fa61019533f..afdc1c2146ee 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -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;
}
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index d01b77887f82..7d763ba22f6f 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -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_ */
diff --git a/include/linux/phy.h b/include/linux/phy.h
index f58eee735a45..169fae4249a9 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -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;
diff --git a/net/ethtool/cabletest.c b/net/ethtool/cabletest.c
index aeb6672a46d0..ae8e63647663 100644
--- a/net/ethtool/cabletest.c
+++ b/net/ethtool/cabletest.c
@@ -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);