diff options
-rw-r--r-- | drivers/net/phy/phy.c | 76 | ||||
-rw-r--r-- | include/linux/phy.h | 28 |
2 files changed, 104 insertions, 0 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 8c22d02b4218..0f4b27215429 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -15,6 +15,7 @@ #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/netdevice.h> +#include <linux/netlink.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/mm.h> @@ -44,6 +45,7 @@ static const char *phy_state_to_str(enum phy_state st) PHY_STATE_STR(UP) PHY_STATE_STR(RUNNING) PHY_STATE_STR(NOLINK) + PHY_STATE_STR(CABLETEST) PHY_STATE_STR(HALTED) } @@ -472,6 +474,62 @@ static void phy_trigger_machine(struct phy_device *phydev) phy_queue_state_machine(phydev, 0); } +static void phy_abort_cable_test(struct phy_device *phydev) +{ + int err; + + err = phy_init_hw(phydev); + if (err) + phydev_err(phydev, "Error while aborting cable test"); +} + +int phy_start_cable_test(struct phy_device *phydev, + struct netlink_ext_ack *extack) +{ + int err; + + if (!(phydev->drv && + phydev->drv->cable_test_start && + phydev->drv->cable_test_get_status)) { + NL_SET_ERR_MSG(extack, + "PHY driver does not support cable testing"); + return -EOPNOTSUPP; + } + + mutex_lock(&phydev->lock); + if (phydev->state == PHY_CABLETEST) { + NL_SET_ERR_MSG(extack, + "PHY already performing a test"); + err = -EBUSY; + goto out; + } + + if (phydev->state < PHY_UP || + phydev->state > PHY_CABLETEST) { + NL_SET_ERR_MSG(extack, + "PHY not configured. Try setting interface up"); + err = -EBUSY; + 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; + } + + phydev->state = PHY_CABLETEST; + +out: + mutex_unlock(&phydev->lock); + + return err; +} +EXPORT_SYMBOL(phy_start_cable_test); + static int phy_config_aneg(struct phy_device *phydev) { if (phydev->drv->config_aneg) @@ -810,6 +868,9 @@ void phy_stop(struct phy_device *phydev) mutex_lock(&phydev->lock); + if (phydev->state == PHY_CABLETEST) + phy_abort_cable_test(phydev); + if (phydev->sfp_bus) sfp_upstream_stop(phydev->sfp_bus); @@ -872,6 +933,7 @@ void phy_state_machine(struct work_struct *work) container_of(dwork, struct phy_device, state_queue); bool needs_aneg = false, do_suspend = false; enum phy_state old_state; + bool finished = false; int err = 0; mutex_lock(&phydev->lock); @@ -890,6 +952,20 @@ void phy_state_machine(struct work_struct *work) case PHY_RUNNING: err = phy_check_link_status(phydev); break; + case PHY_CABLETEST: + err = phydev->drv->cable_test_get_status(phydev, &finished); + if (err) { + phy_abort_cable_test(phydev); + needs_aneg = true; + phydev->state = PHY_UP; + break; + } + + if (finished) { + needs_aneg = true; + phydev->state = PHY_UP; + } + break; case PHY_HALTED: if (phydev->link) { phydev->link = 0; diff --git a/include/linux/phy.h b/include/linux/phy.h index a2b91b5f9d0a..632403fc34f4 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -15,6 +15,7 @@ #include <linux/spinlock.h> #include <linux/ethtool.h> #include <linux/linkmode.h> +#include <linux/netlink.h> #include <linux/mdio.h> #include <linux/mii.h> #include <linux/mii_timestamper.h> @@ -372,6 +373,12 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr); * - irq or timer will set NOLINK if link goes down * - phy_stop moves to HALTED * + * CABLETEST: PHY is performing a cable test. Packet reception/sending + * is not expected to work, carrier will be indicated as down. PHY will be + * poll once per second, or on interrupt for it current state. + * Once complete, move to UP to restart the PHY. + * - phy_stop aborts the running test and moves to HALTED + * * HALTED: PHY is up, but no polling or interrupts are done. Or * PHY is in an error state. * - phy_start moves to UP @@ -383,6 +390,7 @@ enum phy_state { PHY_UP, PHY_RUNNING, PHY_NOLINK, + PHY_CABLETEST, }; /** @@ -689,6 +697,13 @@ struct phy_driver { int (*module_eeprom)(struct phy_device *dev, struct ethtool_eeprom *ee, u8 *data); + /* Start a cable test */ + int (*cable_test_start)(struct phy_device *dev); + /* Once per second, or on interrupt, request the status of the + * test. + */ + int (*cable_test_get_status)(struct phy_device *dev, bool *finished); + /* Get statistics from the phy using ethtool */ int (*get_sset_count)(struct phy_device *dev); void (*get_strings)(struct phy_device *dev, u8 *data); @@ -1227,6 +1242,19 @@ int phy_speed_up(struct phy_device *phydev); int phy_restart_aneg(struct phy_device *phydev); int phy_reset_after_clk_enable(struct phy_device *phydev); +#if IS_ENABLED(CONFIG_PHYLIB) +int phy_start_cable_test(struct phy_device *phydev, + struct netlink_ext_ack *extack); +#else +static inline +int phy_start_cable_test(struct phy_device *phydev, + struct netlink_ext_ack *extack) +{ + NL_SET_ERR_MSG(extack, "Kernel not compiled with PHYLIB support"); + return -EOPNOTSUPP; +} +#endif + static inline void phy_device_reset(struct phy_device *phydev, int value) { mdio_device_reset(&phydev->mdio, value); |