Commit 943bbe1d authored by David S. Miller's avatar David S. Miller

Merge branch 'Raw-PHY-TDR-data'

Andrew Lunn says:

====================
Raw PHY TDR data

Some ethernet PHYs allow access to raw TDR data in addition to summary
diagnostics information. Add support for retrieving this data via
netlink ethtool. The basic structure in the core is the same as for
normal phy diagnostics, the PHY driver simply uses different helpers
to fill the netlink message with different data.

There is a graphical tool under development, as well a ethtool(1)
which can dump the data as text and JSON.

A patched ethtool(1) can be found in
https://github.com/lunn/ethtool.git feature/cable-test-v5

Thanks for Chris Healy for lots of testing.

v2:
See the individual patches but:

Pass distances in centimeters, not meters

Allow the PHY to round distances to what it supports and report what
it actually used along with the results.

Make the Marvell PHY use steps a multiple of 0.805 meters, its native
step size.

v3:
Move the TDR configuration into a structure
Add a range check on step
Use NL_SET_ERR_MSG_ATTR() when appropriate
Move TDR configuration into a nest
Document attributes in the request
Unsquash the last two patches
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 53c0ec4f db8668a1
......@@ -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''
=================================== =====================================
......@@ -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,
},
};
......
......@@ -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;
......
......@@ -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 @@ int phy_start_cable_test(struct phy_device *phydev,
}
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)
......
......@@ -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_ */
......@@ -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);
......
......@@ -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
......
......@@ -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 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
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 @@ int ethnl_cable_test_fault_length(struct phy_device *phydev, u8 pair, u32 cm)
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);
......@@ -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[] = {
......
......@@ -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 */
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