diff options
-rw-r--r-- | Documentation/networking/ethtool-netlink.rst | 97 | ||||
-rw-r--r-- | drivers/net/phy/marvell.c | 285 | ||||
-rw-r--r-- | drivers/net/phy/nxp-tja11xx.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/phy.c | 66 | ||||
-rw-r--r-- | include/linux/ethtool_netlink.h | 25 | ||||
-rw-r--r-- | include/linux/phy.h | 28 | ||||
-rw-r--r-- | include/uapi/linux/ethtool_netlink.h | 76 | ||||
-rw-r--r-- | net/ethtool/cabletest.c | 246 | ||||
-rw-r--r-- | net/ethtool/netlink.c | 5 | ||||
-rw-r--r-- | net/ethtool/netlink.h | 1 |
10 files changed, 818 insertions, 13 deletions
diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 7e651ea33eab..d42661b91128 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -205,6 +205,7 @@ Userspace to kernel: ``ETHTOOL_MSG_EEE_SET`` set EEE settings ``ETHTOOL_MSG_TSINFO_GET`` get timestamping info ``ETHTOOL_MSG_CABLE_TEST_ACT`` action start cable test + ``ETHTOOL_MSG_CABLE_TEST_TDR_ACT`` action start raw TDR cable test ===================================== ================================ Kernel to userspace: @@ -237,6 +238,7 @@ Kernel to userspace: ``ETHTOOL_MSG_EEE_NTF`` EEE settings ``ETHTOOL_MSG_TSINFO_GET_REPLY`` timestamping info ``ETHTOOL_MSG_CABLE_TEST_NTF`` Cable test results + ``ETHTOOL_MSG_CABLE_TEST_TDR_NTF`` Cable test TDR results ===================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -1014,6 +1016,100 @@ information. | | | ``ETHTOOL_A_CABLE_FAULT_LENGTH_CM`` | u32 | length in cm | +-+-+-----------------------------------------+--------+---------------------+ +CABLE_TEST TDR +============== + +Start a cable test and report raw TDR data + +Request contents: + + +--------------------------------------------+--------+-----------------------+ + | ``ETHTOOL_A_CABLE_TEST_TDR_HEADER`` | nested | reply header | + +--------------------------------------------+--------+-----------------------+ + | ``ETHTOOL_A_CABLE_TEST_TDR_CFG`` | nested | test configuration | + +-+------------------------------------------+--------+-----------------------+ + | | ``ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE `` | u32 | first data distance | + +-+-+----------------------------------------+--------+-----------------------+ + | | ``ETHTOOL_A_CABLE_STEP_LAST_DISTANCE `` | u32 | last data distance | + +-+-+----------------------------------------+--------+-----------------------+ + | | ``ETHTOOL_A_CABLE_STEP_STEP_DISTANCE `` | u32 | distance of each step | + +-+-+----------------------------------------+--------+-----------------------+ + | | ``ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR`` | u8 | pair to test | + +-+-+----------------------------------------+--------+-----------------------+ + +The ETHTOOL_A_CABLE_TEST_TDR_CFG is optional, as well as all members +of the nest. All distances are expressed in centimeters. The PHY takes +the distances as a guide, and rounds to the nearest distance it +actually supports. If a pair is passed, only that one pair will be +tested. Otherwise all pairs are tested. + +Notification contents: + +Raw TDR data is gathered by sending a pulse down the cable and +recording the amplitude of the reflected pulse for a given distance. + +It can take a number of seconds to collect TDR data, especial if the +full 100 meters is probed at 1 meter intervals. When the test is +started a notification will be sent containing just +ETHTOOL_A_CABLE_TEST_TDR_STATUS with the value +ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED. + +When the test has completed a second notification will be sent +containing ETHTOOL_A_CABLE_TEST_TDR_STATUS with the value +ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED and the TDR data. + +The message may optionally contain the amplitude of the pulse send +down the cable. This is measured in mV. A reflection should not be +bigger than transmitted pulse. + +Before the raw TDR data should be an ETHTOOL_A_CABLE_TDR_NEST_STEP +nest containing information about the distance along the cable for the +first reading, the last reading, and the step between each +reading. Distances are measured in centimeters. These should be the +exact values the PHY used. These may be different to what the user +requested, if the native measurement resolution is greater than 1 cm. + +For each step along the cable, a ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE is +used to report the amplitude of the reflection for a given pair. + + +---------------------------------------------+--------+----------------------+ + | ``ETHTOOL_A_CABLE_TEST_TDR_HEADER`` | nested | reply header | + +---------------------------------------------+--------+----------------------+ + | ``ETHTOOL_A_CABLE_TEST_TDR_STATUS`` | u8 | completed | + +---------------------------------------------+--------+----------------------+ + | ``ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST`` | nested | all the results | + +-+-------------------------------------------+--------+----------------------+ + | | ``ETHTOOL_A_CABLE_TDR_NEST_PULSE`` | nested | TX Pulse amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_PULSE_mV`` | s16 | Pulse amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | ``ETHTOOL_A_CABLE_NEST_STEP`` | nested | TDR step info | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE ``| u32 | First data distance | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_STEP_LAST_DISTANCE `` | u32 | Last data distance | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_STEP_STEP_DISTANCE `` | u32 | distance of each step| + +-+-+-----------------------------------------+--------+----------------------+ + | | ``ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE`` | nested | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_RESULTS_PAIR`` | u8 | pair number | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_AMPLITUDE_mV`` | s16 | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | ``ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE`` | nested | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_RESULTS_PAIR`` | u8 | pair number | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_AMPLITUDE_mV`` | s16 | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | ``ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE`` | nested | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_RESULTS_PAIR`` | u8 | pair number | + +-+-+-----------------------------------------+--------+----------------------+ + | | | ``ETHTOOL_A_CABLE_AMPLITUDE_mV`` | s16 | Reflection amplitude | + +-+-+-----------------------------------------+--------+----------------------+ + Request translation =================== @@ -1110,4 +1206,5 @@ are netlink only. ``ETHTOOL_GFECPARAM`` n/a ``ETHTOOL_SFECPARAM`` n/a n/a ''ETHTOOL_MSG_CABLE_TEST_ACT'' + n/a ''ETHTOOL_MSG_CABLE_TEST_TDR_ACT'' =================================== ===================================== diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index 4bc7febf9248..2c04e3b2b285 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -42,6 +42,7 @@ #define MII_MARVELL_FIBER_PAGE 0x01 #define MII_MARVELL_MSCR_PAGE 0x02 #define MII_MARVELL_LED_PAGE 0x03 +#define MII_MARVELL_VCT5_PAGE 0x05 #define MII_MARVELL_MISC_TEST_PAGE 0x06 #define MII_MARVELL_VCT7_PAGE 0x07 #define MII_MARVELL_WOL_PAGE 0x11 @@ -164,6 +165,60 @@ #define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */ #define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */ +#define MII_VCT5_TX_RX_MDI0_COUPLING 0x10 +#define MII_VCT5_TX_RX_MDI1_COUPLING 0x11 +#define MII_VCT5_TX_RX_MDI2_COUPLING 0x12 +#define MII_VCT5_TX_RX_MDI3_COUPLING 0x13 +#define MII_VCT5_TX_RX_AMPLITUDE_MASK 0x7f00 +#define MII_VCT5_TX_RX_AMPLITUDE_SHIFT 8 +#define MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION BIT(15) + +#define MII_VCT5_CTRL 0x17 +#define MII_VCT5_CTRL_ENABLE BIT(15) +#define MII_VCT5_CTRL_COMPLETE BIT(14) +#define MII_VCT5_CTRL_TX_SAME_CHANNEL (0x0 << 11) +#define MII_VCT5_CTRL_TX0_CHANNEL (0x4 << 11) +#define MII_VCT5_CTRL_TX1_CHANNEL (0x5 << 11) +#define MII_VCT5_CTRL_TX2_CHANNEL (0x6 << 11) +#define MII_VCT5_CTRL_TX3_CHANNEL (0x7 << 11) +#define MII_VCT5_CTRL_SAMPLES_2 (0x0 << 8) +#define MII_VCT5_CTRL_SAMPLES_4 (0x1 << 8) +#define MII_VCT5_CTRL_SAMPLES_8 (0x2 << 8) +#define MII_VCT5_CTRL_SAMPLES_16 (0x3 << 8) +#define MII_VCT5_CTRL_SAMPLES_32 (0x4 << 8) +#define MII_VCT5_CTRL_SAMPLES_64 (0x5 << 8) +#define MII_VCT5_CTRL_SAMPLES_128 (0x6 << 8) +#define MII_VCT5_CTRL_SAMPLES_DEFAULT (0x6 << 8) +#define MII_VCT5_CTRL_SAMPLES_256 (0x7 << 8) +#define MII_VCT5_CTRL_SAMPLES_SHIFT 8 +#define MII_VCT5_CTRL_MODE_MAXIMUM_PEEK (0x0 << 6) +#define MII_VCT5_CTRL_MODE_FIRST_LAST_PEEK (0x1 << 6) +#define MII_VCT5_CTRL_MODE_OFFSET (0x2 << 6) +#define MII_VCT5_CTRL_SAMPLE_POINT (0x3 << 6) +#define MII_VCT5_CTRL_PEEK_HYST_DEFAULT 3 + +#define MII_VCT5_SAMPLE_POINT_DISTANCE 0x18 +#define MII_VCT5_SAMPLE_POINT_DISTANCE_MAX 511 +#define MII_VCT5_TX_PULSE_CTRL 0x1c +#define MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN BIT(12) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS (0x0 << 10) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_96nS (0x1 << 10) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_64nS (0x2 << 10) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS (0x3 << 10) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_SHIFT 10 +#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_1000mV (0x0 << 8) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_750mV (0x1 << 8) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_500mV (0x2 << 8) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_250mV (0x3 << 8) +#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_SHIFT 8 +#define MII_VCT5_TX_PULSE_CTRL_MAX_AMP BIT(7) +#define MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV (0x6 << 0) + +/* For TDR measurements less than 11 meters, a short pulse should be + * used. + */ +#define TDR_SHORT_CABLE_LENGTH 11 + #define MII_VCT7_PAIR_0_DISTANCE 0x10 #define MII_VCT7_PAIR_1_DISTANCE 0x11 #define MII_VCT7_PAIR_2_DISTANCE 0x12 @@ -220,6 +275,11 @@ struct marvell_priv { u64 stats[ARRAY_SIZE(marvell_hw_stats)]; char *hwmon_name; struct device *hwmon_dev; + bool cable_test_tdr; + u32 first; + u32 last; + u32 step; + s8 pair; }; static int marvell_read_page(struct phy_device *phydev) @@ -1690,7 +1750,150 @@ static void marvell_get_stats(struct phy_device *phydev, data[i] = marvell_get_stat(phydev, i); } -static int marvell_vct7_cable_test_start(struct phy_device *phydev) +static int marvell_vct5_wait_complete(struct phy_device *phydev) +{ + int i; + int val; + + for (i = 0; i < 32; i++) { + val = __phy_read(phydev, MII_VCT5_CTRL); + if (val < 0) + return val; + + if (val & MII_VCT5_CTRL_COMPLETE) + return 0; + } + + phydev_err(phydev, "Timeout while waiting for cable test to finish\n"); + return -ETIMEDOUT; +} + +static int marvell_vct5_amplitude(struct phy_device *phydev, int pair) +{ + int amplitude; + int val; + int reg; + + reg = MII_VCT5_TX_RX_MDI0_COUPLING + pair; + val = __phy_read(phydev, reg); + + if (val < 0) + return 0; + + amplitude = (val & MII_VCT5_TX_RX_AMPLITUDE_MASK) >> + MII_VCT5_TX_RX_AMPLITUDE_SHIFT; + + if (!(val & MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION)) + amplitude = -amplitude; + + return 1000 * amplitude / 128; +} + +static u32 marvell_vct5_distance2cm(int distance) +{ + return distance * 805 / 10; +} + +static u32 marvell_vct5_cm2distance(int cm) +{ + return cm * 10 / 805; +} + +static int marvell_vct5_amplitude_distance(struct phy_device *phydev, + int distance, int pair) +{ + u16 reg; + int err; + int mV; + int i; + + err = __phy_write(phydev, MII_VCT5_SAMPLE_POINT_DISTANCE, + distance); + if (err) + return err; + + reg = MII_VCT5_CTRL_ENABLE | + MII_VCT5_CTRL_TX_SAME_CHANNEL | + MII_VCT5_CTRL_SAMPLES_DEFAULT | + MII_VCT5_CTRL_SAMPLE_POINT | + MII_VCT5_CTRL_PEEK_HYST_DEFAULT; + err = __phy_write(phydev, MII_VCT5_CTRL, reg); + if (err) + return err; + + err = marvell_vct5_wait_complete(phydev); + if (err) + return err; + + for (i = 0; i < 4; i++) { + if (pair != PHY_PAIR_ALL && i != pair) + continue; + + mV = marvell_vct5_amplitude(phydev, i); + ethnl_cable_test_amplitude(phydev, i, mV); + } + + return 0; +} + +static int marvell_vct5_amplitude_graph(struct phy_device *phydev) +{ + struct marvell_priv *priv = phydev->priv; + int distance; + u16 width; + int page; + int err; + u16 reg; + + if (priv->first <= TDR_SHORT_CABLE_LENGTH) + width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS; + else + width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS; + + reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV | + MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN | + MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width; + + err = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE, + MII_VCT5_TX_PULSE_CTRL, reg); + if (err) + return err; + + /* Reading the TDR data is very MDIO heavy. We need to optimize + * access to keep the time to a minimum. So lock the bus once, + * and don't release it until complete. We can then avoid having + * to change the page for every access, greatly speeding things + * up. + */ + page = phy_select_page(phydev, MII_MARVELL_VCT5_PAGE); + if (page < 0) + return page; + + for (distance = priv->first; + distance <= priv->last; + distance += priv->step) { + err = marvell_vct5_amplitude_distance(phydev, distance, + priv->pair); + if (err) + goto restore_page; + + if (distance > TDR_SHORT_CABLE_LENGTH && + width == MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS) { + width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS; + reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV | + MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN | + MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width; + err = __phy_write(phydev, MII_VCT5_TX_PULSE_CTRL, reg); + if (err) + goto restore_page; + } + } + +restore_page: + return phy_restore_page(phydev, page, err); +} + +static int marvell_cable_test_start_common(struct phy_device *phydev) { int bmcr, bmsr, ret; @@ -1719,12 +1922,81 @@ static int marvell_vct7_cable_test_start(struct phy_device *phydev) if (bmsr & BMSR_LSTATUS) msleep(1500); + return 0; +} + +static int marvell_vct7_cable_test_start(struct phy_device *phydev) +{ + struct marvell_priv *priv = phydev->priv; + int ret; + + ret = marvell_cable_test_start_common(phydev); + if (ret) + return ret; + + priv->cable_test_tdr = false; + + /* Reset the VCT5 API control to defaults, otherwise + * VCT7 does not work correctly. + */ + ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE, + MII_VCT5_CTRL, + MII_VCT5_CTRL_TX_SAME_CHANNEL | + MII_VCT5_CTRL_SAMPLES_DEFAULT | + MII_VCT5_CTRL_MODE_MAXIMUM_PEEK | + MII_VCT5_CTRL_PEEK_HYST_DEFAULT); + if (ret) + return ret; + + ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE, + MII_VCT5_SAMPLE_POINT_DISTANCE, 0); + if (ret) + return ret; + return phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE, MII_VCT7_CTRL, MII_VCT7_CTRL_RUN_NOW | MII_VCT7_CTRL_CENTIMETERS); } +static int marvell_vct5_cable_test_tdr_start(struct phy_device *phydev, + const struct phy_tdr_config *cfg) +{ + struct marvell_priv *priv = phydev->priv; + int ret; + + priv->cable_test_tdr = true; + priv->first = marvell_vct5_cm2distance(cfg->first); + priv->last = marvell_vct5_cm2distance(cfg->last); + priv->step = marvell_vct5_cm2distance(cfg->step); + priv->pair = cfg->pair; + + if (priv->first > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX) + return -EINVAL; + + if (priv->last > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX) + return -EINVAL; + + /* Disable VCT7 */ + ret = phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE, + MII_VCT7_CTRL, 0); + if (ret) + return ret; + + ret = marvell_cable_test_start_common(phydev); + if (ret) + return ret; + + ret = ethnl_cable_test_pulse(phydev, 1000); + if (ret) + return ret; + + return ethnl_cable_test_step(phydev, + marvell_vct5_distance2cm(priv->first), + marvell_vct5_distance2cm(priv->last), + marvell_vct5_distance2cm(priv->step)); +} + static int marvell_vct7_distance_to_length(int distance, bool meter) { if (meter) @@ -1828,8 +2100,15 @@ static int marvell_vct7_cable_test_report(struct phy_device *phydev) static int marvell_vct7_cable_test_get_status(struct phy_device *phydev, bool *finished) { + struct marvell_priv *priv = phydev->priv; int ret; + if (priv->cable_test_tdr) { + ret = marvell_vct5_amplitude_graph(phydev); + *finished = true; + return ret; + } + *finished = false; ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE, @@ -2563,6 +2842,7 @@ static struct phy_driver marvell_drivers[] = { .get_tunable = m88e1011_get_tunable, .set_tunable = m88e1011_set_tunable, .cable_test_start = marvell_vct7_cable_test_start, + .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_get_status = marvell_vct7_cable_test_get_status, }, { @@ -2588,6 +2868,7 @@ static struct phy_driver marvell_drivers[] = { .get_tunable = m88e1540_get_tunable, .set_tunable = m88e1540_set_tunable, .cable_test_start = marvell_vct7_cable_test_start, + .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_get_status = marvell_vct7_cable_test_get_status, }, { @@ -2613,6 +2894,7 @@ static struct phy_driver marvell_drivers[] = { .get_tunable = m88e1540_get_tunable, .set_tunable = m88e1540_set_tunable, .cable_test_start = marvell_vct7_cable_test_start, + .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_get_status = marvell_vct7_cable_test_get_status, }, { @@ -2658,6 +2940,7 @@ static struct phy_driver marvell_drivers[] = { .get_tunable = m88e1540_get_tunable, .set_tunable = m88e1540_set_tunable, .cable_test_start = marvell_vct7_cable_test_start, + .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_get_status = marvell_vct7_cable_test_get_status, }, }; diff --git a/drivers/net/phy/nxp-tja11xx.c b/drivers/net/phy/nxp-tja11xx.c index 1e79c30ca81a..a72fa0d2e7c7 100644 --- a/drivers/net/phy/nxp-tja11xx.c +++ b/drivers/net/phy/nxp-tja11xx.c @@ -194,7 +194,7 @@ static int tja11xx_config_aneg_cable_test(struct phy_device *phydev) !phydev->drv->cable_test_get_status) return 0; - ret = ethnl_cable_test_alloc(phydev); + ret = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_NTF); if (ret) return ret; diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 27da0c94818f..1de3938628f4 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -519,7 +519,7 @@ int phy_start_cable_test(struct phy_device *phydev, goto out; } - err = ethnl_cable_test_alloc(phydev); + err = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_NTF); if (err) goto out; @@ -552,6 +552,70 @@ out: } EXPORT_SYMBOL(phy_start_cable_test); +int phy_start_cable_test_tdr(struct phy_device *phydev, + struct netlink_ext_ack *extack, + const struct phy_tdr_config *config) +{ + struct net_device *dev = phydev->attached_dev; + int err = -ENOMEM; + + if (!(phydev->drv && + phydev->drv->cable_test_tdr_start && + phydev->drv->cable_test_get_status)) { + NL_SET_ERR_MSG(extack, + "PHY driver does not support cable test TDR"); + 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; + } + + err = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_TDR_NTF); + if (err) + goto out; + + /* Mark the carrier down until the test is complete */ + phy_link_down(phydev); + + netif_testing_on(dev); + err = phydev->drv->cable_test_tdr_start(phydev, config); + if (err) { + netif_testing_off(dev); + phy_link_up(phydev); + 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); + + return err; +} +EXPORT_SYMBOL(phy_start_cable_test_tdr); + static int phy_config_aneg(struct phy_device *phydev) { if (phydev->drv->config_aneg) diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h index e317fc99565e..8fbe4f97ffad 100644 --- a/include/linux/ethtool_netlink.h +++ b/include/linux/ethtool_netlink.h @@ -17,13 +17,17 @@ enum ethtool_multicast_groups { struct phy_device; #if IS_ENABLED(CONFIG_ETHTOOL_NETLINK) -int ethnl_cable_test_alloc(struct phy_device *phydev); +int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd); void ethnl_cable_test_free(struct phy_device *phydev); void ethnl_cable_test_finished(struct phy_device *phydev); int ethnl_cable_test_result(struct phy_device *phydev, u8 pair, u8 result); int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm); +int ethnl_cable_test_amplitude(struct phy_device *phydev, u8 pair, s16 mV); +int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV); +int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last, + u32 step); #else -static inline int ethnl_cable_test_alloc(struct phy_device *phydev) +static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd) { return -EOPNOTSUPP; } @@ -46,5 +50,22 @@ static inline int ethnl_cable_test_fault_length(struct phy_device *phydev, { return -EOPNOTSUPP; } + +static inline int ethnl_cable_test_amplitude(struct phy_device *phydev, + u8 pair, s16 mV) +{ + return -EOPNOTSUPP; +} + +static inline int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV) +{ + return -EOPNOTSUPP; +} + +static inline int ethnl_cable_test_step(struct phy_device *phydev, u32 first, + u32 last, u32 step) +{ + return -EOPNOTSUPP; +} #endif /* IS_ENABLED(ETHTOOL_NETLINK) */ #endif /* _LINUX_ETHTOOL_NETLINK_H_ */ diff --git a/include/linux/phy.h b/include/linux/phy.h index 6d256e720a66..8c05d0fb5c00 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -548,6 +548,18 @@ struct phy_device { #define to_phy_device(d) container_of(to_mdio_device(d), \ struct phy_device, mdio) +/* A structure containing possible configuration parameters + * for a TDR cable test. The driver does not need to implement + * all the parameters, but should report what is actually used. + */ +struct phy_tdr_config { + u32 first; + u32 last; + u32 step; + s8 pair; +}; +#define PHY_PAIR_ALL -1 + /* struct phy_driver: Driver structure for a particular PHY type * * driver_data: static driver data @@ -699,6 +711,11 @@ struct phy_driver { /* Start a cable test */ int (*cable_test_start)(struct phy_device *dev); + + /* Start a raw TDR cable test */ + int (*cable_test_tdr_start)(struct phy_device *dev, + const struct phy_tdr_config *config); + /* Once per second, or on interrupt, request the status of the * test. */ @@ -1251,6 +1268,9 @@ 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); +int phy_start_cable_test_tdr(struct phy_device *phydev, + struct netlink_ext_ack *extack, + const struct phy_tdr_config *config); #else static inline int phy_start_cable_test(struct phy_device *phydev, @@ -1259,6 +1279,14 @@ int phy_start_cable_test(struct phy_device *phydev, NL_SET_ERR_MSG(extack, "Kernel not compiled with PHYLIB support"); return -EOPNOTSUPP; } +static inline +int phy_start_cable_test_tdr(struct phy_device *phydev, + struct netlink_ext_ack *extack, + const struct phy_tdr_config *config) +{ + NL_SET_ERR_MSG(extack, "Kernel not compiled with PHYLIB support"); + return -EOPNOTSUPP; +} #endif int phy_cable_test_result(struct phy_device *phydev, u8 pair, u16 result); diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h index e6f109b76c9a..fc9051f2eeac 100644 --- a/include/uapi/linux/ethtool_netlink.h +++ b/include/uapi/linux/ethtool_netlink.h @@ -40,6 +40,7 @@ enum { ETHTOOL_MSG_EEE_SET, ETHTOOL_MSG_TSINFO_GET, ETHTOOL_MSG_CABLE_TEST_ACT, + ETHTOOL_MSG_CABLE_TEST_TDR_ACT, /* add new constants above here */ __ETHTOOL_MSG_USER_CNT, @@ -76,6 +77,7 @@ enum { ETHTOOL_MSG_EEE_NTF, ETHTOOL_MSG_TSINFO_GET_REPLY, ETHTOOL_MSG_CABLE_TEST_NTF, + ETHTOOL_MSG_CABLE_TEST_TDR_NTF, /* add new constants above here */ __ETHTOOL_MSG_KERNEL_CNT, @@ -478,6 +480,80 @@ enum { ETHTOOL_A_CABLE_TEST_NTF_MAX = (__ETHTOOL_A_CABLE_TEST_NTF_CNT - 1) }; +/* CABLE TEST TDR */ + +enum { + ETHTOOL_A_CABLE_TEST_TDR_CFG_UNSPEC, + ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST, /* u32 */ + ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST, /* u32 */ + ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP, /* u32 */ + ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR, /* u8 */ + + /* add new constants above here */ + __ETHTOOL_A_CABLE_TEST_TDR_CFG_CNT, + ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX = __ETHTOOL_A_CABLE_TEST_TDR_CFG_CNT - 1 +}; + +enum { + ETHTOOL_A_CABLE_TEST_TDR_UNSPEC, + ETHTOOL_A_CABLE_TEST_TDR_HEADER, /* nest - _A_HEADER_* */ + ETHTOOL_A_CABLE_TEST_TDR_CFG, /* nest - *_TDR_CFG_* */ + + /* add new constants above here */ + __ETHTOOL_A_CABLE_TEST_TDR_CNT, + ETHTOOL_A_CABLE_TEST_TDR_MAX = __ETHTOOL_A_CABLE_TEST_TDR_CNT - 1 +}; + +/* CABLE TEST TDR NOTIFY */ + +enum { + ETHTOOL_A_CABLE_AMPLITUDE_UNSPEC, + ETHTOOL_A_CABLE_AMPLITUDE_PAIR, /* u8 */ + ETHTOOL_A_CABLE_AMPLITUDE_mV, /* s16 */ + + __ETHTOOL_A_CABLE_AMPLITUDE_CNT, + ETHTOOL_A_CABLE_AMPLITUDE_MAX = (__ETHTOOL_A_CABLE_AMPLITUDE_CNT - 1) +}; + +enum { + ETHTOOL_A_CABLE_PULSE_UNSPEC, + ETHTOOL_A_CABLE_PULSE_mV, /* s16 */ + + __ETHTOOL_A_CABLE_PULSE_CNT, + ETHTOOL_A_CABLE_PULSE_MAX = (__ETHTOOL_A_CABLE_PULSE_CNT - 1) +}; + +enum { + ETHTOOL_A_CABLE_STEP_UNSPEC, + ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE, /* u32 */ + ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, /* u32 */ + ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, /* u32 */ + + __ETHTOOL_A_CABLE_STEP_CNT, + ETHTOOL_A_CABLE_STEP_MAX = (__ETHTOOL_A_CABLE_STEP_CNT - 1) +}; + +enum { + ETHTOOL_A_CABLE_TDR_NEST_UNSPEC, + ETHTOOL_A_CABLE_TDR_NEST_STEP, /* nest - ETHTTOOL_A_CABLE_STEP */ + ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE, /* nest - ETHTOOL_A_CABLE_AMPLITUDE */ + ETHTOOL_A_CABLE_TDR_NEST_PULSE, /* nest - ETHTOOL_A_CABLE_PULSE */ + + __ETHTOOL_A_CABLE_TDR_NEST_CNT, + ETHTOOL_A_CABLE_TDR_NEST_MAX = (__ETHTOOL_A_CABLE_TDR_NEST_CNT - 1) +}; + +enum { + ETHTOOL_A_CABLE_TEST_TDR_NTF_UNSPEC, + ETHTOOL_A_CABLE_TEST_TDR_NTF_HEADER, /* nest - ETHTOOL_A_HEADER_* */ + ETHTOOL_A_CABLE_TEST_TDR_NTF_STATUS, /* u8 - _STARTED/_COMPLETE */ + ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST, /* nest - of results: */ + + /* add new constants above here */ + __ETHTOOL_A_CABLE_TEST_TDR_NTF_CNT, + ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX = __ETHTOOL_A_CABLE_TEST_TDR_NTF_CNT - 1 +}; + /* generic netlink info */ #define ETHTOOL_GENL_NAME "ethtool" #define ETHTOOL_GENL_VERSION 1 diff --git a/net/ethtool/cabletest.c b/net/ethtool/cabletest.c index 5ba06eabe8c2..9991688d7d1d 100644 --- a/net/ethtool/cabletest.c +++ b/net/ethtool/cabletest.c @@ -5,7 +5,11 @@ #include "netlink.h" #include "common.h" -/* CABLE_TEST_ACT */ +/* 802.3 standard allows 100 meters for BaseT cables. However longer + * cables might work, depending on the quality of the cables and the + * PHY. So allow testing for up to 150 meters. + */ +#define MAX_CABLE_LENGTH_CM (150 * 100) static const struct nla_policy cable_test_act_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = { @@ -13,7 +17,7 @@ cable_test_act_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = { [ETHTOOL_A_CABLE_TEST_HEADER] = { .type = NLA_NESTED }, }; -static int ethnl_cable_test_started(struct phy_device *phydev) +static int ethnl_cable_test_started(struct phy_device *phydev, u8 cmd) { struct sk_buff *skb; int err = -ENOMEM; @@ -23,7 +27,7 @@ static int ethnl_cable_test_started(struct phy_device *phydev) if (!skb) goto out; - ehdr = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_CABLE_TEST_NTF); + ehdr = ethnl_bcastmsg_put(skb, cmd); if (!ehdr) { err = -EMSGSIZE; goto out; @@ -86,7 +90,8 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info) ethnl_ops_complete(dev); if (!ret) - ethnl_cable_test_started(dev->phydev); + ethnl_cable_test_started(dev->phydev, + ETHTOOL_MSG_CABLE_TEST_NTF); out_rtnl: rtnl_unlock(); @@ -95,16 +100,18 @@ out_dev_put: return ret; } -int ethnl_cable_test_alloc(struct phy_device *phydev) +int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd) { int err = -ENOMEM; - phydev->skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + /* One TDR sample occupies 20 bytes. For a 150 meter cable, + * with four pairs, around 12K is needed. + */ + phydev->skb = genlmsg_new(SZ_16K, GFP_KERNEL); if (!phydev->skb) goto out; - phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, - ETHTOOL_MSG_CABLE_TEST_NTF); + phydev->ehdr = ethnl_bcastmsg_put(phydev->skb, cmd); if (!phydev->ehdr) { err = -EMSGSIZE; goto out; @@ -199,3 +206,226 @@ err: return ret; } EXPORT_SYMBOL_GPL(ethnl_cable_test_fault_length); + +struct cable_test_tdr_req_info { + struct ethnl_req_info base; +}; + +static const struct nla_policy +cable_test_tdr_act_cfg_policy[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .type = NLA_U32 }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .type = NLA_U32 }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .type = NLA_U32 }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .type = NLA_U8 }, +}; + +static const struct nla_policy +cable_test_tdr_act_policy[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_TDR_UNSPEC] = { .type = NLA_REJECT }, + [ETHTOOL_A_CABLE_TEST_TDR_HEADER] = { .type = NLA_NESTED }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .type = NLA_NESTED }, +}; + +/* CABLE_TEST_TDR_ACT */ +int ethnl_act_cable_test_tdr_cfg(const struct nlattr *nest, + struct genl_info *info, + struct phy_tdr_config *cfg) +{ + struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1]; + int ret; + + ret = nla_parse_nested(tb, ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, nest, + cable_test_tdr_act_cfg_policy, info->extack); + if (ret < 0) + return ret; + + if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]) + cfg->first = nla_get_u32( + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST]); + else + cfg->first = 100; + if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]) + cfg->last = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST]); + else + cfg->last = MAX_CABLE_LENGTH_CM; + + if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]) + cfg->step = nla_get_u32(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP]); + else + cfg->step = 100; + + if (tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]) { + cfg->pair = nla_get_u8(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR]); + if (cfg->pair > ETHTOOL_A_CABLE_PAIR_D) { + NL_SET_ERR_MSG_ATTR( + info->extack, + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR], + "invalid pair parameter"); + return -EINVAL; + } + } else { + cfg->pair = PHY_PAIR_ALL; + } + + if (cfg->first > MAX_CABLE_LENGTH_CM) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST], + "invalid first parameter"); + return -EINVAL; + } + + if (cfg->last > MAX_CABLE_LENGTH_CM) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST], + "invalid last parameter"); + return -EINVAL; + } + + if (cfg->first > cfg->last) { + NL_SET_ERR_MSG(info->extack, "invalid first/last parameter"); + return -EINVAL; + } + + if (!cfg->step) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP], + "invalid step parameter"); + return -EINVAL; + } + + if (cfg->step > (cfg->last - cfg->first)) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP], + "step parameter too big"); + return -EINVAL; + } + + return 0; +} + +int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1]; + struct ethnl_req_info req_info = {}; + struct phy_tdr_config cfg; + struct net_device *dev; + int ret; + + ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb, + ETHTOOL_A_CABLE_TEST_TDR_MAX, + cable_test_tdr_act_policy, info->extack); + if (ret < 0) + return ret; + + ret = ethnl_parse_header_dev_get(&req_info, + tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER], + genl_info_net(info), info->extack, + true); + if (ret < 0) + return ret; + + dev = req_info.dev; + if (!dev->phydev) { + ret = -EOPNOTSUPP; + goto out_dev_put; + } + + ret = ethnl_act_cable_test_tdr_cfg(tb[ETHTOOL_A_CABLE_TEST_TDR_CFG], + info, &cfg); + if (ret) + goto out_dev_put; + + rtnl_lock(); + ret = ethnl_ops_begin(dev); + if (ret < 0) + goto out_rtnl; + + ret = phy_start_cable_test_tdr(dev->phydev, info->extack, &cfg); + + ethnl_ops_complete(dev); + + if (!ret) + ethnl_cable_test_started(dev->phydev, + ETHTOOL_MSG_CABLE_TEST_TDR_NTF); + +out_rtnl: + rtnl_unlock(); +out_dev_put: + dev_put(dev); + return ret; +} + +int ethnl_cable_test_amplitude(struct phy_device *phydev, + u8 pair, s16 mV) +{ + struct nlattr *nest; + int ret = -EMSGSIZE; + + nest = nla_nest_start(phydev->skb, + ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u8(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_PAIR, pair)) + goto err; + if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_AMPLITUDE_mV, mV)) + goto err; + + nla_nest_end(phydev->skb, nest); + return 0; + +err: + nla_nest_cancel(phydev->skb, nest); + return ret; +} +EXPORT_SYMBOL_GPL(ethnl_cable_test_amplitude); + +int ethnl_cable_test_pulse(struct phy_device *phydev, u16 mV) +{ + struct nlattr *nest; + int ret = -EMSGSIZE; + + nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_PULSE); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u16(phydev->skb, ETHTOOL_A_CABLE_PULSE_mV, mV)) + goto err; + + nla_nest_end(phydev->skb, nest); + return 0; + +err: + nla_nest_cancel(phydev->skb, nest); + return ret; +} +EXPORT_SYMBOL_GPL(ethnl_cable_test_pulse); + +int ethnl_cable_test_step(struct phy_device *phydev, u32 first, u32 last, + u32 step) +{ + struct nlattr *nest; + int ret = -EMSGSIZE; + + nest = nla_nest_start(phydev->skb, ETHTOOL_A_CABLE_TDR_NEST_STEP); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE, + first)) + goto err; + + if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_LAST_DISTANCE, last)) + goto err; + + if (nla_put_u32(phydev->skb, ETHTOOL_A_CABLE_STEP_STEP_DISTANCE, step)) + goto err; + + nla_nest_end(phydev->skb, nest); + return 0; + +err: + nla_nest_cancel(phydev->skb, nest); + return ret; +} +EXPORT_SYMBOL_GPL(ethnl_cable_test_step); diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 0f2f4754dcf9..88fd07f47040 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -844,6 +844,11 @@ static const struct genl_ops ethtool_genl_ops[] = { .flags = GENL_UNS_ADMIN_PERM, .doit = ethnl_act_cable_test, }, + { + .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_ACT, + .flags = GENL_UNS_ADMIN_PERM, + .doit = ethnl_act_cable_test_tdr, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index b0eb5d920099..9a96b6e90dc2 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -360,5 +360,6 @@ int ethnl_set_coalesce(struct sk_buff *skb, struct genl_info *info); int ethnl_set_pause(struct sk_buff *skb, struct genl_info *info); int ethnl_set_eee(struct sk_buff *skb, struct genl_info *info); int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); +int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info); #endif /* _NET_ETHTOOL_NETLINK_H */ |