Merge branch 'ethtool-introduce-phy-mse-diagnostics-uapi-and-drivers'

Oleksij Rempel says:

====================
ethtool: introduce PHY MSE diagnostics UAPI and drivers

This series introduces a generic kernel-userspace API for retrieving PHY
Mean Square Error (MSE) diagnostics, together with netlink integration,
a fast-path reporting hook in LINKSTATE_GET, and initial driver
implementations for the KSZ9477 and DP83TD510E PHYs.

MSE is defined by the OPEN Alliance "Advanced diagnostic features for
100BASE-T1 automotive Ethernet PHYs" specification [1] as a measure of
slicer error rate, typically used internally to derive the Signal
Quality Indicator (SQI). While SQI is useful as a normalized quality
index, it hides raw measurement data, varies in scaling and thresholds
between vendors, and may not indicate certain failure modes - for
example, cases where autonegotiation would fail even though SQI reports
a good link. In practice, such scenarios can only be investigated in
fixed-link mode; here, MSE can provide an empirically estimated value
indicating conditions under which autonegotiation would not succeed.

Example output with current implementation:
root@DistroKit:~ ethtool lan1
Settings for lan1:
...
        Speed: 1000Mb/s
        Duplex: Full
...
        Link detected: yes
        SQI: 5/7
        MSE: 3/127 (channel: worst)

root@DistroKit:~ ethtool --show-mse lan1
MSE diagnostics for lan1:
MSE Configuration:
        Max Average MSE: 127
        Refresh Rate: 2000000 ps
        Symbols per Sample: 250
        Supported capabilities: average channel-a channel-b channel-c
                                channel-d worst

MSE Snapshot (Channel: a):
        Average MSE: 4

MSE Snapshot (Channel: b):
        Average MSE: 3

MSE Snapshot (Channel: c):
        Average MSE: 2

MSE Snapshot (Channel: d):
        Average MSE: 3

[1] https://opensig.org/wp-content/uploads/2024/01/Advanced_PHY_features_for_automotive_Ethernet_V1.0.pdf
====================

Link: https://patch.msgid.link/20251027122801.982364-1-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2025-11-03 18:32:33 -08:00
10 changed files with 897 additions and 1 deletions

View File

@@ -1823,6 +1823,73 @@ attribute-sets:
type: uint
enum: pse-event
doc: List of events reported by the PSE controller
-
name: mse-capabilities
doc: MSE capabilities attribute set
attr-cnt-name: --ethtool-a-mse-capabilities-cnt
attributes:
-
name: max-average-mse
type: uint
-
name: max-peak-mse
type: uint
-
name: refresh-rate-ps
type: uint
-
name: num-symbols
type: uint
-
name: mse-snapshot
doc: MSE snapshot attribute set
attr-cnt-name: --ethtool-a-mse-snapshot-cnt
attributes:
-
name: average-mse
type: uint
-
name: peak-mse
type: uint
-
name: worst-peak-mse
type: uint
-
name: mse
attr-cnt-name: --ethtool-a-mse-cnt
attributes:
-
name: header
type: nest
nested-attributes: header
-
name: capabilities
type: nest
nested-attributes: mse-capabilities
-
name: channel-a
type: nest
nested-attributes: mse-snapshot
-
name: channel-b
type: nest
nested-attributes: mse-snapshot
-
name: channel-c
type: nest
nested-attributes: mse-snapshot
-
name: channel-d
type: nest
nested-attributes: mse-snapshot
-
name: worst-channel
type: nest
nested-attributes: mse-snapshot
-
name: link
type: nest
nested-attributes: mse-snapshot
operations:
enum-model: directional
@@ -2756,6 +2823,25 @@ operations:
attributes:
- header
- context
-
name: mse-get
doc: Get PHY MSE measurement data and capabilities.
attribute-set: mse
do: &mse-get-op
request:
attributes:
- header
reply:
attributes:
- header
- capabilities
- channel-a
- channel-b
- channel-c
- channel-d
- worst-channel
- link
dump: *mse-get-op
mcast-groups:
list:

View File

@@ -242,6 +242,7 @@ Userspace to kernel:
``ETHTOOL_MSG_RSS_SET`` set RSS settings
``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context
``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context
``ETHTOOL_MSG_MSE_GET`` get MSE diagnostic data
===================================== =================================
Kernel to userspace:
@@ -299,6 +300,7 @@ Kernel to userspace:
``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context
``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created
``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted
``ETHTOOL_MSG_MSE_GET_REPLY`` MSE diagnostic data
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
@@ -2458,6 +2460,68 @@ Kernel response contents:
For a description of each attribute, see ``TSCONFIG_GET``.
MSE_GET
=======
Retrieves detailed Mean Square Error (MSE) diagnostic information from the PHY.
Request Contents:
==================================== ====== ============================
``ETHTOOL_A_MSE_HEADER`` nested request header
==================================== ====== ============================
Kernel Response Contents:
==================================== ====== ================================
``ETHTOOL_A_MSE_HEADER`` nested reply header
``ETHTOOL_A_MSE_CAPABILITIES`` nested capability/scale info for MSE
measurements
``ETHTOOL_A_MSE_CHANNEL_A`` nested snapshot for Channel A
``ETHTOOL_A_MSE_CHANNEL_B`` nested snapshot for Channel B
``ETHTOOL_A_MSE_CHANNEL_C`` nested snapshot for Channel C
``ETHTOOL_A_MSE_CHANNEL_D`` nested snapshot for Channel D
``ETHTOOL_A_MSE_WORST_CHANNEL`` nested snapshot for worst channel
``ETHTOOL_A_MSE_LINK`` nested snapshot for link-wide aggregate
==================================== ====== ================================
MSE Capabilities
----------------
This nested attribute reports the capability / scaling properties used to
interpret snapshot values.
============================================== ====== =========================
``ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE`` uint max avg_mse scale
``ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE`` uint max peak_mse scale
``ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS`` uint sample rate (picoseconds)
``ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS`` uint symbols per HW sample
============================================== ====== =========================
The max-average/peak fields are included only if the corresponding metric
is supported by the PHY. Their absence indicates that the metric is not
available.
See ``struct phy_mse_capability`` kernel documentation in
``include/linux/phy.h``.
MSE Snapshot
------------
Each per-channel nest contains an atomic snapshot of MSE values for that
selector (channel A/B/C/D, worst channel, or link).
========================================== ====== ===================
``ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE`` uint average MSE value
``ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE`` uint current peak MSE
``ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE`` uint worst-case peak MSE
========================================== ====== ===================
Within each channel nest, only the metrics supported by the PHY will be present.
See ``struct phy_mse_snapshot`` kernel documentation in
``include/linux/phy.h``.
Request translation
===================

View File

@@ -61,6 +61,7 @@
#define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15)
#define DP83TD510E_MSE_DETECT 0xa85
#define DP83TD510E_MSE_MAX U16_MAX
#define DP83TD510_SQI_MAX 7
@@ -249,6 +250,64 @@ struct dp83td510_priv {
#define DP83TD510E_ALCD_COMPLETE BIT(15)
#define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0)
static int dp83td510_get_mse_capability(struct phy_device *phydev,
struct phy_mse_capability *cap)
{
/* DP83TD510E documents only a single (average) MSE register
* (used to derive SQI); no peak or worst-peak counters are
* described. Advertise only PHY_MSE_CAP_AVG.
*/
cap->supported_caps = PHY_MSE_CAP_AVG;
/* 10BASE-T1L is a single-pair medium, so there are no B/C/D channels.
* We still advertise PHY_MSE_CAP_CHANNEL_A to indicate that the PHY
* can attribute the measurement to a specific pair (the only one),
* rather than exposing it only as a link-aggregate.
*
* Rationale:
* - Keeps the ethtool MSE_GET selection logic consistent: per-channel
* (A/B/C/D) is preferred over WORST/LINK, so userspace receives a
* CHANNEL_A nest instead of LINK.
* - Signals to tools that "per-pair" data is available (even if there's
* just one pair), avoiding the impression that only aggregate values
* are supported.
* - Remains compatible with multi-pair PHYs and uniform UI handling.
*
* Note: WORST and other channels are not advertised on 10BASE-T1L.
*/
cap->supported_caps |= PHY_MSE_CHANNEL_A | PHY_MSE_CAP_LINK;
cap->max_average_mse = DP83TD510E_MSE_MAX;
/* The datasheet does not specify the refresh rate or symbol count,
* but based on similar PHYs and standards, we can assume a common
* value. For 10BASE-T1L, the symbol rate is 7.5 MBd. A common
* diagnostic interval is around 1ms.
* 7.5e6 symbols/sec * 0.001 sec = 7500 symbols.
*/
cap->refresh_rate_ps = 1000000000; /* 1 ms */
cap->num_symbols = 7500;
return 0;
}
static int dp83td510_get_mse_snapshot(struct phy_device *phydev,
enum phy_mse_channel channel,
struct phy_mse_snapshot *snapshot)
{
int ret;
if (channel != PHY_MSE_CHANNEL_LINK &&
channel != PHY_MSE_CHANNEL_A)
return -EOPNOTSUPP;
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT);
if (ret < 0)
return ret;
snapshot->average_mse = ret;
return 0;
}
static int dp83td510_led_brightness_set(struct phy_device *phydev, u8 index,
enum led_brightness brightness)
{
@@ -893,6 +952,9 @@ static struct phy_driver dp83td510_driver[] = {
.get_phy_stats = dp83td510_get_phy_stats,
.update_stats = dp83td510_update_stats,
.get_mse_capability = dp83td510_get_mse_capability,
.get_mse_snapshot = dp83td510_get_mse_snapshot,
.led_brightness_set = dp83td510_led_brightness_set,
.led_hw_is_supported = dp83td510_led_hw_is_supported,
.led_hw_control_set = dp83td510_led_hw_control_set,

View File

@@ -2325,6 +2325,106 @@ static int kszphy_get_sqi_max(struct phy_device *phydev)
return KSZ9477_SQI_MAX;
}
static int kszphy_get_mse_capability(struct phy_device *phydev,
struct phy_mse_capability *cap)
{
/* Capabilities depend on link mode:
* - 1000BASE-T: per-pair SQI registers exist => expose A..D
* and a WORST selector.
* - 100BASE-TX: HW provides a single MSE/SQI reading in the "channel A"
* register, but with auto MDI-X there is no MDI-X resolution bit,
* so we cannot map that register to a specific wire pair reliably.
* To avoid misleading per-channel data, advertise only LINK.
* Other speeds: no MSE exposure via this driver.
*
* Note: WORST is *not* a hardware selector on this family.
* We expose it because the driver computes it in software
* by scanning per-channel readouts (A..D) and picking the
* maximum average MSE.
*/
if (phydev->speed == SPEED_1000)
cap->supported_caps = PHY_MSE_CAP_CHANNEL_A |
PHY_MSE_CAP_CHANNEL_B |
PHY_MSE_CAP_CHANNEL_C |
PHY_MSE_CAP_CHANNEL_D |
PHY_MSE_CAP_WORST_CHANNEL;
else if (phydev->speed == SPEED_100)
cap->supported_caps = PHY_MSE_CAP_LINK;
else
return -EOPNOTSUPP;
cap->max_average_mse = FIELD_MAX(KSZ9477_MMD_SQI_MASK);
cap->refresh_rate_ps = 2000000; /* 2 us */
/* Estimated from link modulation (125 MBd per channel) and documented
* refresh rate of 2 us
*/
cap->num_symbols = 250;
cap->supported_caps |= PHY_MSE_CAP_AVG;
return 0;
}
static int kszphy_get_mse_snapshot(struct phy_device *phydev,
enum phy_mse_channel channel,
struct phy_mse_snapshot *snapshot)
{
u8 num_channels;
int ret;
if (phydev->speed == SPEED_1000)
num_channels = 4;
else if (phydev->speed == SPEED_100)
num_channels = 1;
else
return -EOPNOTSUPP;
if (channel == PHY_MSE_CHANNEL_WORST) {
u32 worst_val = 0;
int i;
/* WORST is implemented in software: select the maximum
* average MSE across the available per-channel registers.
* Only defined when multiple channels exist (1000BASE-T).
*/
if (num_channels < 2)
return -EOPNOTSUPP;
for (i = 0; i < num_channels; i++) {
ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + i);
if (ret < 0)
return ret;
ret = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret);
if (ret > worst_val)
worst_val = ret;
}
snapshot->average_mse = worst_val;
} else if (channel == PHY_MSE_CHANNEL_LINK && num_channels == 1) {
ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A);
if (ret < 0)
return ret;
snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret);
} else if (channel >= PHY_MSE_CHANNEL_A &&
channel <= PHY_MSE_CHANNEL_D) {
/* Per-channel readouts are valid only for 1000BASE-T. */
if (phydev->speed != SPEED_1000)
return -EOPNOTSUPP;
ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + channel);
if (ret < 0)
return ret;
snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret);
} else {
return -EOPNOTSUPP;
}
return 0;
}
static void kszphy_enable_clk(struct phy_device *phydev)
{
struct kszphy_priv *priv = phydev->priv;
@@ -6497,6 +6597,8 @@ static struct phy_driver ksphy_driver[] = {
.cable_test_get_status = ksz9x31_cable_test_get_status,
.get_sqi = kszphy_get_sqi,
.get_sqi_max = kszphy_get_sqi_max,
.get_mse_capability = kszphy_get_mse_capability,
.get_mse_snapshot = kszphy_get_mse_snapshot,
} };
module_phy_driver(ksphy_driver);

View File

@@ -903,6 +903,165 @@ struct phy_led {
#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
/*
* PHY_MSE_CAP_* - Bitmask flags for Mean Square Error (MSE) capabilities
*
* These flags describe which MSE metrics and selectors are implemented
* by the PHY for the current link mode. They are used in
* struct phy_mse_capability.supported_caps.
*
* Standardization:
* The OPEN Alliance (OA) defines the presence of MSE/SQI/pMSE but not their
* numeric scaling, update intervals, or aggregation windows. See:
* OA 100BASE-T1 TC1 v1.0, sections 6.1.1-6.1.3
* OA 1000BASE-T1 TC12 v2.2, sections 6.1.1-6.1.2
*
* Description of flags:
*
* PHY_MSE_CAP_CHANNEL_A
* Per-pair diagnostics for Channel A are supported. Mapping to the
* physical wire pair may depend on MDI/MDI-X polarity.
*
* PHY_MSE_CAP_CHANNEL_B, _C, _D
* Same as above for channels B-D.
*
* PHY_MSE_CAP_WORST_CHANNEL
* The PHY or driver can identify and report the single worst-performing
* channel without querying each one individually.
*
* PHY_MSE_CAP_LINK
* The PHY provides only a link-wide aggregate measurement or cannot map
* results to a specific pair (for example 100BASE-TX with unknown
* MDI/MDI-X).
*
* PHY_MSE_CAP_AVG
* Average MSE (mean DCQ metric) is supported. For 100/1000BASE-T1 the OA
* recommends 2^16 symbols, scaled 0..511, but the exact scaling is
* vendor-specific.
*
* PHY_MSE_CAP_PEAK
* Peak MSE (current peak within the measurement window) is supported.
* Defined as pMSE for 100BASE-T1; vendor-specific for others.
*
* PHY_MSE_CAP_WORST_PEAK
* Latched worst-case peak MSE since the last read (read-to-clear if
* implemented). Optional in OA 100BASE-T1 TC1 6.1.3.
*/
#define PHY_MSE_CAP_CHANNEL_A BIT(0)
#define PHY_MSE_CAP_CHANNEL_B BIT(1)
#define PHY_MSE_CAP_CHANNEL_C BIT(2)
#define PHY_MSE_CAP_CHANNEL_D BIT(3)
#define PHY_MSE_CAP_WORST_CHANNEL BIT(4)
#define PHY_MSE_CAP_LINK BIT(5)
#define PHY_MSE_CAP_AVG BIT(6)
#define PHY_MSE_CAP_PEAK BIT(7)
#define PHY_MSE_CAP_WORST_PEAK BIT(8)
/*
* enum phy_mse_channel - Identifiers for selecting MSE measurement channels
*
* PHY_MSE_CHANNEL_A - PHY_MSE_CHANNEL_D
* Select per-pair measurement for the corresponding channel.
*
* PHY_MSE_CHANNEL_WORST
* Select the single worst-performing channel reported by hardware.
*
* PHY_MSE_CHANNEL_LINK
* Select link-wide aggregate data (used when per-pair results are
* unavailable).
*/
enum phy_mse_channel {
PHY_MSE_CHANNEL_A,
PHY_MSE_CHANNEL_B,
PHY_MSE_CHANNEL_C,
PHY_MSE_CHANNEL_D,
PHY_MSE_CHANNEL_WORST,
PHY_MSE_CHANNEL_LINK,
};
/**
* struct phy_mse_capability - Capabilities of Mean Square Error (MSE)
* measurement interface
*
* Standardization notes:
*
* - Presence of MSE/SQI/pMSE is defined by OPEN Alliance specs, but numeric
* scaling, refresh/update rate and aggregation windows are not fixed and
* are vendor-/product-specific. (OA 100BASE-T1 TC1 v1.0 6.1.*;
* OA 1000BASE-T1 TC12 v2.2 6.1.*)
*
* - Typical recommendations: 2^16 symbols and 0..511 scaling for MSE; pMSE only
* defined for 100BASE-T1 (sliding window example), others are vendor
* extensions. Drivers must report actual scale/limits here.
*
* Describes the MSE measurement capabilities for the current link mode. These
* properties are dynamic and may change when link settings are modified.
* Callers should re-query this capability after any link state change to
* ensure they have the most up-to-date information.
*
* Callers should only request measurements for channels and types that are
* indicated as supported by the @supported_caps bitmask. If @supported_caps
* is 0, the device provides no MSE diagnostics, and driver operations should
* typically return -EOPNOTSUPP.
*
* Snapshot values for average and peak MSE can be normalized to a 0..1 ratio
* by dividing the raw snapshot by the corresponding @max_average_mse or
* @max_peak_mse value.
*
* @max_average_mse: The maximum value for an average MSE snapshot. This
* defines the scale for the measurement. If the PHY_MSE_CAP_AVG capability is
* supported, this value MUST be greater than 0. (vendor-specific units).
* @max_peak_mse: The maximum value for a peak MSE snapshot. If either
* PHY_MSE_CAP_PEAK or PHY_MSE_CAP_WORST_PEAK is supported, this value MUST
* be greater than 0. (vendor-specific units).
* @refresh_rate_ps: The typical interval, in picoseconds, between hardware
* updates of the MSE values. This is an estimate, and callers should not
* assume synchronous sampling. (vendor-specific units).
* @num_symbols: The number of symbols aggregated per hardware sample to
* calculate the MSE. (vendor-specific units).
* @supported_caps: A bitmask of PHY_MSE_CAP_* values indicating which
* measurement types (e.g., average, peak) and channels
* (e.g., per-pair or link-wide) are supported.
*/
struct phy_mse_capability {
u64 max_average_mse;
u64 max_peak_mse;
u64 refresh_rate_ps;
u64 num_symbols;
u32 supported_caps;
};
/**
* struct phy_mse_snapshot - A snapshot of Mean Square Error (MSE) diagnostics
*
* Holds a set of MSE diagnostic values that were all captured from a single
* measurement window.
*
* Values are raw, device-scaled and not normalized. Use struct
* phy_mse_capability to interpret the scale and sampling window.
*
* @average_mse: The average MSE value over the measurement window.
* OPEN Alliance references MSE as a DCQ metric; recommends 2^16 symbols and
* 0..511 scaling. Exact scale and refresh are vendor-specific.
* (100BASE-T1 TC1 v1.0 6.1.1; 1000BASE-T1 TC12 v2.2 6.1.1).
*
* @peak_mse: The peak MSE value observed within the measurement window.
* For 100BASE-T1, "pMSE" is optional and may be implemented via a sliding
* 128-symbol window with periodic capture; not standardized for 1000BASE-T1.
* (100BASE-T1 TC1 v1.0 6.1.3, Table "DCQ.peakMSE").
*
* @worst_peak_mse: A latched high-water mark of the peak MSE since last read
* (read-to-clear if implemented). OPEN Alliance shows a latched "worst case
* peak MSE" for 100BASE-T1 pMSE; availability/semantics outside that are
* vendor-specific. (100BASE-T1 TC1 v1.0 6.1.3, DCQ.peakMSE high byte;
* 1000BASE-T1 TC12 v2.2 treats DCQ details as vendor-specific.)
*/
struct phy_mse_snapshot {
u64 average_mse;
u64 peak_mse;
u64 worst_peak_mse;
};
/**
* struct phy_driver - Driver structure for a particular PHY type
*
@@ -1184,6 +1343,53 @@ struct phy_driver {
/** @get_sqi_max: Get the maximum signal quality indication */
int (*get_sqi_max)(struct phy_device *dev);
/**
* @get_mse_capability: Get capabilities and scale of MSE measurement
* @dev: PHY device
* @cap: Output (filled on success)
*
* Fill @cap with the PHY's MSE capability for the current
* link mode: scale limits (max_average_mse, max_peak_mse), update
* interval (refresh_rate_ps), sample length (num_symbols) and the
* capability bitmask (supported_caps).
*
* Implementations may defer capability report until hardware has
* converged; in that case they should return -EAGAIN and allow the
* caller to retry later.
*
* Return: 0 on success. On failure, returns a negative errno code, such
* as -EOPNOTSUPP if MSE measurement is not supported by the PHY or in
* the current link mode, or -EAGAIN if the capability information is
* not yet available.
*/
int (*get_mse_capability)(struct phy_device *dev,
struct phy_mse_capability *cap);
/**
* @get_mse_snapshot: Retrieve a snapshot of MSE diagnostic values
* @dev: PHY device
* @channel: Channel identifier (PHY_MSE_CHANNEL_*)
* @snapshot: Output (filled on success)
*
* Fill @snapshot with a correlated set of MSE values from the most
* recent measurement window.
*
* Callers must validate @channel against supported_caps returned by
* get_mse_capability(). Drivers must not coerce @channel; if the
* requested selector is not implemented by the device or current link
* mode, the operation must fail.
*
* worst_peak_mse is latched and must be treated as read-to-clear.
*
* Return: 0 on success. On failure, returns a negative errno code, such
* as -EOPNOTSUPP if MSE measurement is not supported by the PHY or in
* the current link mode, or -EAGAIN if measurements are not yet
* available.
*/
int (*get_mse_snapshot)(struct phy_device *dev,
enum phy_mse_channel channel,
struct phy_mse_snapshot *snapshot);
/* PLCA RS interface */
/** @get_plca_cfg: Return the current PLCA configuration */
int (*get_plca_cfg)(struct phy_device *dev,

View File

@@ -803,6 +803,39 @@ enum {
ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
};
enum {
ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE = 1,
ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
__ETHTOOL_A_MSE_CAPABILITIES_CNT,
ETHTOOL_A_MSE_CAPABILITIES_MAX = (__ETHTOOL_A_MSE_CAPABILITIES_CNT - 1)
};
enum {
ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE = 1,
ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
__ETHTOOL_A_MSE_SNAPSHOT_CNT,
ETHTOOL_A_MSE_SNAPSHOT_MAX = (__ETHTOOL_A_MSE_SNAPSHOT_CNT - 1)
};
enum {
ETHTOOL_A_MSE_HEADER = 1,
ETHTOOL_A_MSE_CAPABILITIES,
ETHTOOL_A_MSE_CHANNEL_A,
ETHTOOL_A_MSE_CHANNEL_B,
ETHTOOL_A_MSE_CHANNEL_C,
ETHTOOL_A_MSE_CHANNEL_D,
ETHTOOL_A_MSE_WORST_CHANNEL,
ETHTOOL_A_MSE_LINK,
__ETHTOOL_A_MSE_CNT,
ETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1)
};
enum {
ETHTOOL_MSG_USER_NONE = 0,
ETHTOOL_MSG_STRSET_GET = 1,
@@ -855,6 +888,7 @@ enum {
ETHTOOL_MSG_RSS_SET,
ETHTOOL_MSG_RSS_CREATE_ACT,
ETHTOOL_MSG_RSS_DELETE_ACT,
ETHTOOL_MSG_MSE_GET,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -915,6 +949,7 @@ enum {
ETHTOOL_MSG_RSS_CREATE_ACT_REPLY,
ETHTOOL_MSG_RSS_CREATE_NTF,
ETHTOOL_MSG_RSS_DELETE_NTF,
ETHTOOL_MSG_MSE_GET_REPLY,
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)

View File

@@ -9,4 +9,4 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \
module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o \
phy.o tsconfig.o
phy.o tsconfig.o mse.o

329
net/ethtool/mse.c Normal file
View File

@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include "netlink.h"
#include "common.h"
/* Channels A-D only; WORST and LINK are exclusive alternatives */
#define PHY_MSE_CHANNEL_COUNT 4
struct mse_req_info {
struct ethnl_req_info base;
};
struct mse_snapshot_entry {
struct phy_mse_snapshot snapshot;
int channel;
};
struct mse_reply_data {
struct ethnl_reply_data base;
struct phy_mse_capability capability;
struct mse_snapshot_entry *snapshots;
unsigned int num_snapshots;
};
static struct mse_reply_data *
mse_repdata(const struct ethnl_reply_data *reply_base)
{
return container_of(reply_base, struct mse_reply_data, base);
}
const struct nla_policy ethnl_mse_get_policy[] = {
[ETHTOOL_A_MSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static int get_snapshot_if_supported(struct phy_device *phydev,
struct mse_reply_data *data,
unsigned int *idx, u32 cap_bit,
enum phy_mse_channel channel)
{
int ret;
if (data->capability.supported_caps & cap_bit) {
ret = phydev->drv->get_mse_snapshot(phydev, channel,
&data->snapshots[*idx].snapshot);
if (ret)
return ret;
data->snapshots[*idx].channel = channel;
(*idx)++;
}
return 0;
}
static int mse_get_channels(struct phy_device *phydev,
struct mse_reply_data *data)
{
unsigned int i = 0;
int ret;
if (!data->capability.supported_caps)
return 0;
data->snapshots = kcalloc(PHY_MSE_CHANNEL_COUNT,
sizeof(*data->snapshots), GFP_KERNEL);
if (!data->snapshots)
return -ENOMEM;
/* Priority 1: Individual channels */
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_A,
PHY_MSE_CHANNEL_A);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_B,
PHY_MSE_CHANNEL_B);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_C,
PHY_MSE_CHANNEL_C);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_D,
PHY_MSE_CHANNEL_D);
if (ret)
return ret;
/* If any individual channels were found, we are done. */
if (i > 0) {
data->num_snapshots = i;
return 0;
}
/* Priority 2: Worst channel, if no individual channels supported. */
ret = get_snapshot_if_supported(phydev, data, &i,
PHY_MSE_CAP_WORST_CHANNEL,
PHY_MSE_CHANNEL_WORST);
if (ret)
return ret;
/* If worst channel was found, we are done. */
if (i > 0) {
data->num_snapshots = i;
return 0;
}
/* Priority 3: Link-wide, if nothing else is supported. */
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_LINK,
PHY_MSE_CHANNEL_LINK);
if (ret)
return ret;
data->num_snapshots = i;
return 0;
}
static int mse_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
const struct genl_info *info)
{
struct mse_reply_data *data = mse_repdata(reply_base);
struct net_device *dev = reply_base->dev;
struct phy_device *phydev;
int ret;
phydev = ethnl_req_get_phydev(req_base, info->attrs,
ETHTOOL_A_MSE_HEADER, info->extack);
if (IS_ERR(phydev))
return PTR_ERR(phydev);
if (!phydev)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret)
return ret;
mutex_lock(&phydev->lock);
if (!phydev->drv || !phydev->drv->get_mse_capability ||
!phydev->drv->get_mse_snapshot) {
ret = -EOPNOTSUPP;
goto out_unlock;
}
if (!phydev->link) {
ret = -ENETDOWN;
goto out_unlock;
}
ret = phydev->drv->get_mse_capability(phydev, &data->capability);
if (ret)
goto out_unlock;
ret = mse_get_channels(phydev, data);
out_unlock:
mutex_unlock(&phydev->lock);
ethnl_ops_complete(dev);
if (ret)
kfree(data->snapshots);
return ret;
}
static void mse_cleanup_data(struct ethnl_reply_data *reply_base)
{
struct mse_reply_data *data = mse_repdata(reply_base);
kfree(data->snapshots);
}
static int mse_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct mse_reply_data *data = mse_repdata(reply_base);
size_t len = 0;
unsigned int i;
/* ETHTOOL_A_MSE_CAPABILITIES */
len += nla_total_size(0);
if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
/* ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE */
len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
PHY_MSE_CAP_WORST_PEAK))
/* ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE */
len += nla_total_size(sizeof(u64));
/* ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS */
len += nla_total_size(sizeof(u64));
/* ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS */
len += nla_total_size(sizeof(u64));
for (i = 0; i < data->num_snapshots; i++) {
size_t snapshot_len = 0;
/* Per-channel nest (e.g., ETHTOOL_A_MSE_CHANNEL_A / _B / _C /
* _D / _WORST_CHANNEL / _LINK)
*/
snapshot_len += nla_total_size(0);
if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
snapshot_len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & PHY_MSE_CAP_PEAK)
snapshot_len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK)
snapshot_len += nla_total_size(sizeof(u64));
len += snapshot_len;
}
return len;
}
static int mse_channel_to_attr(int ch)
{
switch (ch) {
case PHY_MSE_CHANNEL_A:
return ETHTOOL_A_MSE_CHANNEL_A;
case PHY_MSE_CHANNEL_B:
return ETHTOOL_A_MSE_CHANNEL_B;
case PHY_MSE_CHANNEL_C:
return ETHTOOL_A_MSE_CHANNEL_C;
case PHY_MSE_CHANNEL_D:
return ETHTOOL_A_MSE_CHANNEL_D;
case PHY_MSE_CHANNEL_WORST:
return ETHTOOL_A_MSE_WORST_CHANNEL;
case PHY_MSE_CHANNEL_LINK:
return ETHTOOL_A_MSE_LINK;
default:
return -EINVAL;
}
}
static int mse_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct mse_reply_data *data = mse_repdata(reply_base);
struct nlattr *nest;
unsigned int i;
int ret;
nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES);
if (!nest)
return -EMSGSIZE;
if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE,
data->capability.max_average_mse);
if (ret < 0)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
PHY_MSE_CAP_WORST_PEAK)) {
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
data->capability.max_peak_mse);
if (ret < 0)
goto nla_put_nest_failure;
}
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
data->capability.refresh_rate_ps);
if (ret < 0)
goto nla_put_nest_failure;
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
data->capability.num_symbols);
if (ret < 0)
goto nla_put_nest_failure;
nla_nest_end(skb, nest);
for (i = 0; i < data->num_snapshots; i++) {
const struct mse_snapshot_entry *s = &data->snapshots[i];
int chan_attr;
chan_attr = mse_channel_to_attr(s->channel);
if (chan_attr < 0)
return chan_attr;
nest = nla_nest_start(skb, chan_attr);
if (!nest)
return -EMSGSIZE;
if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE,
s->snapshot.average_mse);
if (ret)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) {
ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
s->snapshot.peak_mse);
if (ret)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
s->snapshot.worst_peak_mse);
if (ret)
goto nla_put_nest_failure;
}
nla_nest_end(skb, nest);
}
return 0;
nla_put_nest_failure:
nla_nest_cancel(skb, nest);
return ret;
}
const struct ethnl_request_ops ethnl_mse_request_ops = {
.request_cmd = ETHTOOL_MSG_MSE_GET,
.reply_cmd = ETHTOOL_MSG_MSE_GET_REPLY,
.hdr_attr = ETHTOOL_A_MSE_HEADER,
.req_info_size = sizeof(struct mse_req_info),
.reply_data_size = sizeof(struct mse_reply_data),
.prepare_data = mse_prepare_data,
.cleanup_data = mse_cleanup_data,
.reply_size = mse_reply_size,
.fill_reply = mse_fill_reply,
};

View File

@@ -420,6 +420,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_TSCONFIG_GET] = &ethnl_tsconfig_request_ops,
[ETHTOOL_MSG_TSCONFIG_SET] = &ethnl_tsconfig_request_ops,
[ETHTOOL_MSG_PHY_GET] = &ethnl_phy_request_ops,
[ETHTOOL_MSG_MSE_GET] = &ethnl_mse_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1534,6 +1535,15 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_rss_delete_policy,
.maxattr = ARRAY_SIZE(ethnl_rss_delete_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_MSE_GET,
.doit = ethnl_default_doit,
.start = ethnl_perphy_start,
.dumpit = ethnl_perphy_dumpit,
.done = ethnl_perphy_done,
.policy = ethnl_mse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

View File

@@ -442,6 +442,7 @@ extern const struct ethnl_request_ops ethnl_plca_status_request_ops;
extern const struct ethnl_request_ops ethnl_mm_request_ops;
extern const struct ethnl_request_ops ethnl_phy_request_ops;
extern const struct ethnl_request_ops ethnl_tsconfig_request_ops;
extern const struct ethnl_request_ops ethnl_mse_request_ops;
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
@@ -497,6 +498,7 @@ extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE
extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];
extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEADER + 1];
extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1];
extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER + 1];
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);