Commit a68a8138 authored by Andrew Lunn's avatar Andrew Lunn Committed by Jakub Kicinski

net: phy: Add cable test support to state machine

Running a cable test is desruptive to normal operation of the PHY and
can take a 5 to 10 seconds to complete. The RTNL lock cannot be held
for this amount of time, and add a new state to the state machine for
running a cable test.

The driver is expected to implement two functions. The first is used
to start a cable test. Once the test has started, it should return.

The second function is called once per second, or on interrupt to
check if the cable test is complete, and to allow the PHY to report
the status.

v2:
Rename phy_cable_test_abort to phy_abort_cable_test
Return different extack when already running test
Use phy_init_hw() to reset the PHY
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Signed-off-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent b9f96423
......@@ -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;
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment