Add the base infrastructure for Mean Square Error (MSE) diagnostics, as proposed by the OPEN Alliance "Advanced diagnostic features for 100BASE-T1 automotive Ethernet PHYs" [1] specification. The OPEN Alliance spec defines only average MSE and average peak MSE over a fixed number of symbols. However, other PHYs, such as the KSZ9131, additionally expose a worst-peak MSE value latched since the last channel capture. This API accounts for such vendor extensions by adding a distinct capability bit and snapshot field. Channel-to-pair mapping is normally straightforward, but in some cases (e.g. 100BASE-TX with MDI-X resolution unknown) the mapping is ambiguous. If hardware does not expose MDI-X status, the exact pair cannot be determined. To avoid returning misleading per-channel data in this case, a LINK selector is defined for aggregate MSE measurements. All investigated devices differ in MSE capabilities, such as sample rate, number of analyzed symbols, and scaling factors. For example, the KSZ9131 uses different scaling for MSE and pMSE. To make this visible to userspace, scale limits and timing information are returned via get_mse_capability(). Some PHYs sample very few symbols at high frequency (e.g. 2 us update rate). To cover such cases and allow for future high-speed PHYs with even shorter intervals, the refresh rate is reported as u64 in picoseconds. This patch defines new UAPI enums for MSE capability flags and channel selectors in ethtool_netlink (generated from YAML), kernel-side `struct phy_mse_capability` and `struct phy_mse_snapshot`, and new phy_driver ops: - get_mse_capability(): report supported capabilities, scaling, and sampling parameters for the current link mode - get_mse_snapshot(): retrieve a correlated set of MSE values from the latest measurement window These definitions form the core API; no driver implements them yet. Standardization notes: OPEN Alliance defines presence and interpretation of some metrics but does not fix numeric scales or sampling internals: - SQI (3-bit, 0..7) is mandatory; correlation to SNR/BER is informative (OA 100BASE-T1 v1.0 6.1.2; OA 1000BASE-T1 v2.2 6.1.2). - MSE is optional; OA recommends 2^16 symbols and scaling to 0..511, with a worst-case latch since last read (OA 100BASE-T1 v1.0 6.1.1; OA 1000BASE-T1 v2.2 6.1.1). Refresh is recommended (~0.8-2.0 ms for 100BASE-T1; ~80-200 us for 1000BASE-T1). Exact scaling/time windows are vendor-specific. - Peak MSE (pMSE) is defined only for 100BASE-T1 as optional, e.g. 128-symbol sliding window with 8-bit range and worst-case latch (OA 100BASE-T1 v1.0 6.1.3). Therefore this UAPI exposes which measures and selectors a PHY supports, and documents where behavior is standard-referenced vs vendor-specific. [1] Signed-off-by: Oleksij Rempel --- changes v6: - YAML: generate mask for phy-mse-cap- - YAML: Reorder phy-mse-capability flags to list channels first, then measures - YAML: Drop explicit value: 0 on phy-mse-channel’s first enumerator - YAML: Expand/clarify docs: add a short Standardization block - UAPI: regenerate ethtool_netlink_generated.h with the new flag ordering - phy.h: Remove channel field from struct phy_mse_snapshot - phy.h: Use u64 for all snapshot/capability scalar fields (average_mse, peak_mse, worst_peak_mse, max_*, refresh_rate_ps, num_symbols) for range/consistency with netlink. - phy.h: Update kerneldoc: note values are raw, device-scaled; point to phy_mse_capability for interpretation; add brief OA references; changes v5: - clarify @channel direction in struct phy_mse_snapshot - add per-field spec references for snapshot values - refine YAML docstrings for phy-mse-channel and link selector - update standardization notes (OA v1.0 and v2.2) changes v4: - remove -ENETDOWN as expected error value for get_mse_capability() and get_mse_snapshot() - fix htmldocs builds --- Documentation/netlink/specs/ethtool.yaml | 91 +++++++++++++ include/linux/phy.h | 127 ++++++++++++++++++ .../uapi/linux/ethtool_netlink_generated.h | 69 ++++++++++ 3 files changed, 287 insertions(+) diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 6a0fb1974513..4c352f36d57d 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -211,6 +211,97 @@ definitions: name: discard value: 31 + - + name: phy-mse-capability + doc: | + Bitmask flags for MSE capabilities. + Standardization: + - SQI/MSE/pMSE presence is defined by OPEN Alliance, but numeric scaling, + update intervals and aggregation windows are vendor-/product-specific. + See OA 100BASE-T1 v1.0 6.1.1-6.1.3 and OA 1000BASE-T1 v2.2 6.1.1-6.1.2. + (References are informative; drivers must not assume identical scales.) + + These flags are used in the 'supported_caps' field of struct + phy_mse_capability to indicate which measurement capabilities are + supported by the PHY hardware. + type: flags + name-prefix: phy-mse-cap- + render-max: true + entries: + - + name: channel-a + doc: Diagnostics for Channel A are supported. (API selector; mapping to + physical pair may depend on MDI/MDI-X status.) + - + name: channel-b + doc: Diagnostics for Channel B are supported. + - + name: channel-c + doc: Diagnostics for Channel C are supported. + - + name: channel-d + doc: Diagnostics for Channel D are supported. + - + name: worst-channel + doc: | + Hardware or drivers can identify the single worst-performing channel + without needing to query each one individually. + - + name: link + doc: | + Hardware provides only a link-wide aggregate MSE or cannot map + the measurement to a specific channel/pair. Typical for media where + the MDI/MDI-X resolution or pair mapping is unknown (e.g. 100BASE-TX). + - + name: avg + doc: Average MSE supported (OA-referenced metric; scaling/window are + vendor-specific). For 100/1000BASE-T1 recommended 2^16 symbols, scaled + 0..511; device-specific otherwise. (OA 100BASE-T1 6.1.1; + OA 1000BASE-T1 6.1.1) + - + name: peak + doc: Peak MSE supported (current peak over the last measurement window). + Defined as pMSE only for 100BASE-T1 in OA; other variants are vendor + extensions. (OA 100BASE-T1 6.1.3) + - + name: worst-peak + doc: Latched worst-case peak since last read (read-to-clear if + implemented). Optional in OA for 100BASE-T1 pMSE. + (OA 100BASE-T1 6.1.3) + + - + name: phy-mse-channel + doc: | + Identifiers for the 'channel' parameter used to select which diagnostic + data to retrieve. + type: enum + name-prefix: phy-mse-channel- + entries: + - + name: a + doc: Request data for channel A. + - + name: b + doc: Request data for channel B. + - + name: c + doc: Request data for channel C. + - + name: d + doc: Request data for channel D. + - + name: worst + doc: | + Request data for the single worst-performing channel. This is a + convenience for PHYs or drivers that can identify the worst channel + in hardware. + - + name: link + doc: | + Request data for the link as a whole. Use when the PHY exposes only + a link-wide aggregate MSE or cannot attribute results to any single + channel/pair (e.g. 100BASE-TX with unknown MDI/MDI-X mapping). + attribute-sets: - name: header diff --git a/include/linux/phy.h b/include/linux/phy.h index 3c7634482356..fe3ad28b3614 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -903,6 +903,87 @@ struct phy_led { #define to_phy_led(d) container_of(d, struct phy_led, led_cdev) +/** + * 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 v1.0 6.1.*; + * OA 1000BASE-T1 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 v1.0 6.1.1; 1000BASE-T1 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 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 v1.0 6.1.3, DCQ.peakMSE high byte; 1000BASE-T1 + * 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 +1265,52 @@ 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, u32 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, diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 0e8ac0d974e2..03954c324ff8 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -77,6 +77,75 @@ enum ethtool_pse_event { ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64, }; +/** + * enum ethtool_phy_mse_capability - Bitmask flags for MSE capabilities. + * Standardization: - SQI/MSE/pMSE presence is defined by OPEN Alliance, but + * numeric scaling, update intervals and aggregation windows are + * vendor-/product-specific. See OA 100BASE-T1 v1.0 6.1.1-6.1.3 and OA + * 1000BASE-T1 v2.2 6.1.1-6.1.2. (References are informative; drivers must + * not assume identical scales.) These flags are used in the 'supported_caps' + * field of struct phy_mse_capability to indicate which measurement + * capabilities are supported by the PHY hardware. + * @PHY_MSE_CAP_CHANNEL_A: Diagnostics for Channel A are supported. (API + * selector; mapping to physical pair may depend on MDI/MDI-X status.) + * @PHY_MSE_CAP_CHANNEL_B: Diagnostics for Channel B are supported. + * @PHY_MSE_CAP_CHANNEL_C: Diagnostics for Channel C are supported. + * @PHY_MSE_CAP_CHANNEL_D: Diagnostics for Channel D are supported. + * @PHY_MSE_CAP_WORST_CHANNEL: Hardware or drivers can identify the single + * worst-performing channel without needing to query each one individually. + * @PHY_MSE_CAP_LINK: Hardware provides only a link-wide aggregate MSE or + * cannot map the measurement to a specific channel/pair. Typical for media + * where the MDI/MDI-X resolution or pair mapping is unknown (e.g. + * 100BASE-TX). + * @PHY_MSE_CAP_AVG: Average MSE supported (OA-referenced metric; + * scaling/window are vendor-specific). For 100/1000BASE-T1 recommended 2^16 + * symbols, scaled 0..511; device-specific otherwise. (OA 100BASE-T1 6.1.1; + * OA 1000BASE-T1 6.1.1) + * @PHY_MSE_CAP_PEAK: Peak MSE supported (current peak over the last + * measurement window). Defined as pMSE only for 100BASE-T1 in OA; other + * variants are vendor extensions. (OA 100BASE-T1 6.1.3) + * @PHY_MSE_CAP_WORST_PEAK: Latched worst-case peak since last read + * (read-to-clear if implemented). Optional in OA for 100BASE-T1 pMSE. (OA + * 100BASE-T1 6.1.3) + */ +enum ethtool_phy_mse_capability { + PHY_MSE_CAP_CHANNEL_A = 1, + PHY_MSE_CAP_CHANNEL_B = 2, + PHY_MSE_CAP_CHANNEL_C = 4, + PHY_MSE_CAP_CHANNEL_D = 8, + PHY_MSE_CAP_WORST_CHANNEL = 16, + PHY_MSE_CAP_LINK = 32, + PHY_MSE_CAP_AVG = 64, + PHY_MSE_CAP_PEAK = 128, + PHY_MSE_CAP_WORST_PEAK = 256, + + /* private: */ + PHY_MSE_CAP_MASK = 511, +}; + +/** + * enum ethtool_phy_mse_channel - Identifiers for the 'channel' parameter used + * to select which diagnostic data to retrieve. + * @PHY_MSE_CHANNEL_A: Request data for channel A. + * @PHY_MSE_CHANNEL_B: Request data for channel B. + * @PHY_MSE_CHANNEL_C: Request data for channel C. + * @PHY_MSE_CHANNEL_D: Request data for channel D. + * @PHY_MSE_CHANNEL_WORST: Request data for the single worst-performing + * channel. This is a convenience for PHYs or drivers that can identify the + * worst channel in hardware. + * @PHY_MSE_CHANNEL_LINK: Request data for the link as a whole. Use when the + * PHY exposes only a link-wide aggregate MSE or cannot attribute results to + * any single channel/pair (e.g. 100BASE-TX with unknown MDI/MDI-X mapping). + */ +enum ethtool_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, +}; + enum { ETHTOOL_A_HEADER_UNSPEC, ETHTOOL_A_HEADER_DEV_INDEX, -- 2.47.3 Introduce the userspace entry point for PHY MSE diagnostics via ethtool netlink. This exposes the core API added previously and returns both capability information and one or more snapshots. Userspace sends ETHTOOL_MSG_MSE_GET with an optional channel selector. The reply carries: - ETHTOOL_A_MSE_CAPABILITIES: scale limits, timing, and supported capability bitmask - ETHTOOL_A_MSE_CHANNEL_* nests: one or more snapshots (per-channel if available, otherwise WORST, otherwise LINK) If no channel is requested, the kernel returns snapshots for all supported selectors. Requests for unsupported selectors fail with -EOPNOTSUPP; link down returns -ENETDOWN. Changes: - YAML: add attribute sets (mse, mse-capabilities, mse-snapshot) and the mse-get operation - UAPI (generated): add ETHTOOL_A_MSE_* enums and message IDs, ETHTOOL_MSG_MSE_GET/REPLY - ethtool core: add net/ethtool/mse.c implementing the request, register genl op, and hook into ethnl dispatch - docs: document MSE_GET in ethtool-netlink.rst The include/uapi/linux/ethtool_netlink_generated.h is generated from Documentation/netlink/specs/ethtool.yaml. Signed-off-by: Oleksij Rempel --- changes v6: - YAML: rename mse-config -> mse-capabilities; rename top-level attr "config" -> "capabilities". - YAML: drop all explicit UNSPEC entries; start enums at 1 with _CNT/_MAX tails. - YAML: switch scalar fields to type: uint; remove pad attrs. - YAML: per-channel reply layout: replace multi-attr "snapshot" with fixed nests ETHTOOL_A_MSE_CHANNEL_A/B/C/D/WORST_CHANNEL/LINK; drop inner "channel" field. - UAPI: regenerate include/uapi/linux/ethtool_netlink_generated.h - mse.c: implement capabilities container and per-channel snapshot nests; use nla_put_uint() for all YAML uint fields; size using sizeof(u64) in reply_size(); no pad usage. - mse.c: encode supported-caps with ethnl_bitset32_size()/ethnl_put_bitset32(); - mse.c: return -ENETDOWN on link down (commit message updated accordingly). - docs: ethtool-netlink.rst updated to new attribute names/layout and terminology (capabilities, per-channel nests). changes v5: - add struct phy_mse_snapshot and phy_mse_config in the documentation changes v4: - s/__ethtool-a-mse/--ethtool-a-mse - remove duplicate kernel-doc line - fix htmldocs compile warnings --- Documentation/netlink/specs/ethtool.yaml | 94 ++++ Documentation/networking/ethtool-netlink.rst | 71 +++ include/uapi/linux/ethtool.h | 2 + .../uapi/linux/ethtool_netlink_generated.h | 37 ++ net/ethtool/Makefile | 2 +- net/ethtool/common.c | 13 + net/ethtool/common.h | 2 + net/ethtool/mse.c | 411 ++++++++++++++++++ net/ethtool/netlink.c | 10 + net/ethtool/netlink.h | 2 + net/ethtool/strset.c | 5 + 11 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 net/ethtool/mse.c diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 4c352f36d57d..a4531500dfb5 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -1914,6 +1914,80 @@ attribute-sets: type: uint enum: pse-event doc: List of events reported by the PSE controller + - + name: mse-capabilities + 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: supported-caps + type: nest + nested-attributes: bitset + enum: phy-mse-capability + - + name: mse-snapshot + 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: channel + type: uint + enum: phy-mse-channel + - + 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 @@ -2847,6 +2921,26 @@ 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 + - channel + reply: + attributes: + - header + - capabilities + - channel-a + - channel-b + - channel-c + - channel-d + - worst-channel + - link + dump: *mse-get-op mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index b270886c5f5d..3206f791f56d 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -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,75 @@ 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 + ``ETHTOOL_A_MSE_CHANNEL`` uint optional channel enum value + ==================================== ====== ============================ + +.. kernel-doc:: include/uapi/linux/ethtool_netlink_generated.h + :identifiers: ethtool_phy_mse_channel + +The optional ``ETHTOOL_A_MSE_CHANNEL`` attribute allows the caller to request +data for a specific channel. If omitted, the kernel will return snapshots for +all supported channels. + +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 + ``ETHTOOL_A_MSE_CAPABILITIES_SUPPORTED_CAPS`` bitset bitmask of + phy_mse_capability + =========================================== ====== ========================= + +.. kernel-doc:: include/uapi/linux/ethtool_netlink_generated.h + :identifiers: ethtool_phy_mse_capability + +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 + ========================================== ====== =================== + +See ``struct phy_mse_snapshot`` kernel documentation in +``include/linux/phy.h``. + Request translation =================== diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 8bd5ea5469d9..df9bbfdb2a09 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -683,6 +683,7 @@ enum ethtool_link_ext_substate_module { * @ETH_SS_STATS_RMON: names of RMON statistics * @ETH_SS_STATS_PHY: names of PHY(dev) statistics * @ETH_SS_TS_FLAGS: hardware timestamping flags + * @ETH_SS_MSE_CAPS: Mean Square Error (MSE) capability bit names * * @ETH_SS_COUNT: number of defined string sets */ @@ -710,6 +711,7 @@ enum ethtool_stringset { ETH_SS_STATS_RMON, ETH_SS_STATS_PHY, ETH_SS_TS_FLAGS, + ETH_SS_MSE_CAPS, /* add new constants above here */ ETH_SS_COUNT diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 03954c324ff8..481aadf8cdba 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -872,6 +872,41 @@ 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_SUPPORTED_CAPS, + + __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_CHANNEL, + 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, @@ -924,6 +959,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) @@ -984,6 +1020,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) diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile index 1e493553b977..629c10916670 100644 --- a/net/ethtool/Makefile +++ b/net/ethtool/Makefile @@ -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 diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 55223ebc2a7e..42c7f33f1c8c 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -521,6 +521,19 @@ const char udp_tunnel_type_names[][ETH_GSTRING_LEN] = { static_assert(ARRAY_SIZE(udp_tunnel_type_names) == __ETHTOOL_UDP_TUNNEL_TYPE_CNT); +const char mse_cap_names[][ETH_GSTRING_LEN] = { + [const_ilog2(PHY_MSE_CAP_CHANNEL_A)] = "channel-a", + [const_ilog2(PHY_MSE_CAP_CHANNEL_B)] = "channel-b", + [const_ilog2(PHY_MSE_CAP_CHANNEL_C)] = "channel-c", + [const_ilog2(PHY_MSE_CAP_CHANNEL_D)] = "channel-d", + [const_ilog2(PHY_MSE_CAP_WORST_CHANNEL)] = "worst-channel", + [const_ilog2(PHY_MSE_CAP_LINK)] = "link", + [const_ilog2(PHY_MSE_CAP_AVG)] = "average-mse", + [const_ilog2(PHY_MSE_CAP_PEAK)] = "peak-mse", + [const_ilog2(PHY_MSE_CAP_WORST_PEAK)] = "worst-peak-mse", +}; +static_assert(ARRAY_SIZE(mse_cap_names) == __MSE_CAP_CNT); + /* return false if legacy contained non-0 deprecated fields * maxtxpkt/maxrxpkt. rest of ksettings always updated */ diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 1609cf4e53eb..da4af3d69e3e 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -14,6 +14,7 @@ #define __SOF_TIMESTAMPING_CNT (const_ilog2(SOF_TIMESTAMPING_LAST) + 1) #define __HWTSTAMP_FLAG_CNT (const_ilog2(HWTSTAMP_FLAG_LAST) + 1) +#define __MSE_CAP_CNT (const_ilog2(PHY_MSE_CAP_MASK) + 1) struct genl_info; struct hwtstamp_provider_desc; @@ -34,6 +35,7 @@ extern const char ts_tx_type_names[][ETH_GSTRING_LEN]; extern const char ts_rx_filter_names[][ETH_GSTRING_LEN]; extern const char ts_flags_names[][ETH_GSTRING_LEN]; extern const char udp_tunnel_type_names[][ETH_GSTRING_LEN]; +extern const char mse_cap_names[][ETH_GSTRING_LEN]; int __ethtool_get_link(struct net_device *dev); diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c new file mode 100644 index 000000000000..89365bdb1109 --- /dev/null +++ b/net/ethtool/mse.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "netlink.h" +#include "common.h" +#include "bitset.h" + +#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 inline 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), + [ETHTOOL_A_MSE_CHANNEL] = { .type = NLA_U32 }, +}; + +static inline int mse_caps_size(u32 caps, bool compact) +{ + return ethnl_bitset32_size(&caps, NULL, __MSE_CAP_CNT, + mse_cap_names, compact); +} + +static inline int mse_caps_put(struct sk_buff *skb, int attrtype, + u32 caps, bool compact) +{ + return ethnl_put_bitset32(skb, attrtype, &caps, NULL, + __MSE_CAP_CNT, mse_cap_names, compact); +} + +static int get_snapshot_if_supported(struct phy_device *phydev, + struct mse_reply_data *data, + unsigned int *idx, u32 cap_bit, + int channel_id) +{ + int ret; + + if (data->capability.supported_caps & cap_bit) { + ret = phydev->drv->get_mse_snapshot(phydev, channel_id, + &data->snapshots[*idx].snapshot); + if (ret) + return ret; + data->snapshots[*idx].channel = channel_id; + (*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_get_one_channel(struct phy_device *phydev, + struct mse_reply_data *data, int channel) +{ + u32 cap_bit = 0; + int ret; + + switch (channel) { + case PHY_MSE_CHANNEL_A: + cap_bit = PHY_MSE_CAP_CHANNEL_A; + break; + case PHY_MSE_CHANNEL_B: + cap_bit = PHY_MSE_CAP_CHANNEL_B; + break; + case PHY_MSE_CHANNEL_C: + cap_bit = PHY_MSE_CAP_CHANNEL_C; + break; + case PHY_MSE_CHANNEL_D: + cap_bit = PHY_MSE_CAP_CHANNEL_D; + break; + case PHY_MSE_CHANNEL_WORST: + cap_bit = PHY_MSE_CAP_WORST_CHANNEL; + break; + case PHY_MSE_CHANNEL_LINK: + cap_bit = PHY_MSE_CAP_LINK; + break; + default: + return -EINVAL; + } + + if (!(data->capability.supported_caps & cap_bit)) + return -EOPNOTSUPP; + + data->snapshots = kzalloc(sizeof(*data->snapshots), GFP_KERNEL); + if (!data->snapshots) + return -ENOMEM; + + ret = phydev->drv->get_mse_snapshot(phydev, channel, + &data->snapshots[0].snapshot); + if (ret) + return ret; + + data->snapshots[0].channel = channel; + data->num_snapshots = 1; + 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; + + if (info->attrs[ETHTOOL_A_MSE_CHANNEL]) { + u32 channel = nla_get_u32(info->attrs[ETHTOOL_A_MSE_CHANNEL]); + + ret = mse_get_one_channel(phydev, data, channel); + } else { + 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; + int ret; + + /* 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)); + /* ETHTOOL_A_MSE_CAPABILITIES_SUPPORTED_CAPS */ + ret = mse_caps_size(data->capability.supported_caps, + req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS); + if (ret < 0) + return ret; + len += ret; + + 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_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 *cap_nest, *snap_nest; + unsigned int i; + int ret; + + cap_nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES); + if (!cap_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_cap_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_cap_failure; + } + + ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS, + data->capability.refresh_rate_ps); + if (ret < 0) + goto nla_put_cap_failure; + + ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS, + data->capability.num_symbols); + if (ret < 0) + goto nla_put_cap_failure; + + ret = mse_caps_put(skb, ETHTOOL_A_MSE_CAPABILITIES_SUPPORTED_CAPS, + data->capability.supported_caps, + req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS); + if (ret < 0) + goto nla_put_cap_failure; + + nla_nest_end(skb, cap_nest); + + for (i = 0; i < data->num_snapshots; i++) { + const struct mse_snapshot_entry *s = &data->snapshots[i]; + int chan_attr; + + switch (s->channel) { + case PHY_MSE_CHANNEL_A: + chan_attr = ETHTOOL_A_MSE_CHANNEL_A; + break; + case PHY_MSE_CHANNEL_B: + chan_attr = ETHTOOL_A_MSE_CHANNEL_B; + break; + case PHY_MSE_CHANNEL_C: + chan_attr = ETHTOOL_A_MSE_CHANNEL_C; + break; + case PHY_MSE_CHANNEL_D: + chan_attr = ETHTOOL_A_MSE_CHANNEL_D; + break; + case PHY_MSE_CHANNEL_WORST: + chan_attr = ETHTOOL_A_MSE_WORST_CHANNEL; + break; + case PHY_MSE_CHANNEL_LINK: + chan_attr = ETHTOOL_A_MSE_LINK; + break; + default: + return -EINVAL; + } + + snap_nest = nla_nest_start(skb, chan_attr); + if (!snap_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_snap_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_snap_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_snap_failure; + } + + nla_nest_end(skb, snap_nest); + } + + return 0; + +nla_put_cap_failure: + nla_nest_cancel(skb, cap_nest); + return ret; + +nla_put_snap_failure: + nla_nest_cancel(skb, snap_nest); + return -EMSGSIZE; +} + +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, +}; diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 2f813f25f07e..6e5f0f4f815a 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -420,6 +420,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { [ETHTOOL_MSG_TSCONFIG_GET] = ðnl_tsconfig_request_ops, [ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops, [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops, + [ETHTOOL_MSG_MSE_GET] = ðnl_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[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index 1d4f9ecb3d26..f9ebcfb327a6 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -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_CHANNEL + 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); diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c index f6a67109beda..1c86549db88d 100644 --- a/net/ethtool/strset.c +++ b/net/ethtool/strset.c @@ -115,6 +115,11 @@ static const struct strset_info info_template[] = { .count = __ETHTOOL_A_STATS_PHY_CNT, .strings = stats_phy_names, }, + [ETH_SS_MSE_CAPS] = { + .per_dev = false, + .count = __MSE_CAP_CNT, + .strings = mse_cap_names, + }, }; struct strset_req_info { -- 2.47.3 Extend ETHTOOL_MSG_LINKSTATE_GET to optionally return a simplified Mean Square Error (MSE) reading alongside existing link status fields. The new attributes are: - ETHTOOL_A_LINKSTATE_MSE_VALUE: current average MSE value - ETHTOOL_A_LINKSTATE_MSE_MAX: scale limit for the reported value - ETHTOOL_A_LINKSTATE_MSE_CHANNEL: source channel selector This path reuses the PHY MSE core API (struct phy_mse_capability and struct phy_mse_snapshot), but only retrieves a single value intended for quick link-health checks: * If the PHY supports a WORST channel selector, report its current average MSE. * Otherwise, if LINK-wide measurements are supported, report those. * If neither is available, omit the attributes. Unlike the full MSE_GET interface, LINKSTATE_GET does not expose per-channel or peak/worst-peak values and incurs minimal overhead. Drivers that implement get_mse_capability() / get_mse_snapshot() will automatically populate this data. The intent is to provide tooling with a "fast path" health indicator without issuing a separate MSE_GET request, though the long-term overlap with the full interface may need reevaluation. Signed-off-by: Oleksij Rempel Reviewed-by: Kory Maincent --- changes v6: - rename struct phy_mse_config -> struct phy_mse_capability (consistency with v6 API) - switch ETHTOOL_A_LINKSTATE_MSE_VALUE and ETHTOOL_A_LINKSTATE_MSE_MAX from u32 -> uint (64-bit capable) changes v3: - add missing yaml spec --- Documentation/netlink/specs/ethtool.yaml | 10 ++ Documentation/networking/ethtool-netlink.rst | 11 +++ .../uapi/linux/ethtool_netlink_generated.h | 3 + net/ethtool/linkstate.c | 94 +++++++++++++++++++ 4 files changed, 118 insertions(+) diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index a4531500dfb5..6069047a4498 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -721,6 +721,16 @@ attribute-sets: - name: ext-down-cnt type: u32 + - + name: mse-value + type: uint + - + name: mse-max + type: uint + - + name: mse-channel + type: uint + enum: phy-mse-channel - name: debug attr-cnt-name: __ethtool-a-debug-cnt diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index 3206f791f56d..b20ac248cbb7 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -530,6 +530,9 @@ Kernel response contents: ``ETHTOOL_A_LINKSTATE_EXT_STATE`` u8 link extended state ``ETHTOOL_A_LINKSTATE_EXT_SUBSTATE`` u8 link extended substate ``ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT`` u32 count of link down events + ``ETHTOOL_A_LINKSTATE_MSE_VALUE`` uint Current average MSE value + ``ETHTOOL_A_LINKSTATE_MSE_MAX`` uint Max scale for average MSE + ``ETHTOOL_A_LINKSTATE_MSE_CHANNEL`` uint Source of MSE value ==================================== ====== ============================ For most NIC drivers, the value of ``ETHTOOL_A_LINKSTATE_LINK`` returns @@ -541,6 +544,14 @@ optional values. ethtool core can provide either both ``ETHTOOL_A_LINKSTATE_EXT_STATE`` and ``ETHTOOL_A_LINKSTATE_EXT_SUBSTATE``, or only ``ETHTOOL_A_LINKSTATE_EXT_STATE``, or none of them. +``ETHTOOL_A_LINKSTATE_MSE_VALUE`` and ``ETHTOOL_A_LINKSTATE_MSE_MAX`` are +optional values. The MSE value provided by this interface is a lightweight, +less detailed version for quick health checks. If a WORST-channel selector is +supported, the value comes from that selector; otherwise, if a LINK-wide +selector is supported, it is used. If neither is available, the attributes are +omitted. ``ETHTOOL_A_LINKSTATE_MSE_CHANNEL`` carries an +``ethtool_phy_mse_channel``. + ``LINKSTATE_GET`` allows dump requests (kernel returns reply messages for all devices supporting the request). diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 481aadf8cdba..3a5c0fd579a6 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -337,6 +337,9 @@ enum { ETHTOOL_A_LINKSTATE_EXT_STATE, ETHTOOL_A_LINKSTATE_EXT_SUBSTATE, ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT, + ETHTOOL_A_LINKSTATE_MSE_VALUE, + ETHTOOL_A_LINKSTATE_MSE_MAX, + ETHTOOL_A_LINKSTATE_MSE_CHANNEL, __ETHTOOL_A_LINKSTATE_CNT, ETHTOOL_A_LINKSTATE_MAX = (__ETHTOOL_A_LINKSTATE_CNT - 1) diff --git a/net/ethtool/linkstate.c b/net/ethtool/linkstate.c index 05a5f72c99fa..cc6bf7273102 100644 --- a/net/ethtool/linkstate.c +++ b/net/ethtool/linkstate.c @@ -14,6 +14,9 @@ struct linkstate_reply_data { int link; int sqi; int sqi_max; + u64 mse_value; + u64 mse_max; + u64 mse_channel; struct ethtool_link_ext_stats link_stats; bool link_ext_state_provided; struct ethtool_link_ext_state_info ethtool_link_ext_state_info; @@ -76,6 +79,72 @@ static bool linkstate_sqi_valid(struct linkstate_reply_data *data) data->sqi <= data->sqi_max; } +static int linkstate_get_mse(struct phy_device *phydev, + struct linkstate_reply_data *data) +{ + struct phy_mse_snapshot snapshot = {}; + struct phy_mse_capability cap = {}; + int channel, ret; + + if (!phydev) + return -EOPNOTSUPP; + + mutex_lock(&phydev->lock); + + if (!phydev->drv || !phydev->drv->get_mse_capability || + !phydev->drv->get_mse_snapshot) { + ret = -EOPNOTSUPP; + goto unlock; + } + + if (!phydev->link) { + ret = -ENETDOWN; + goto unlock; + } + + ret = phydev->drv->get_mse_capability(phydev, &cap); + if (ret) + goto unlock; + + /* We only expose average MSE on LINKSTATE. */ + if (!(cap.supported_caps & PHY_MSE_CAP_AVG)) { + ret = -EOPNOTSUPP; + goto unlock; + } + + if (cap.supported_caps & PHY_MSE_CAP_WORST_CHANNEL) { + channel = PHY_MSE_CHANNEL_WORST; + } else if (cap.supported_caps & PHY_MSE_CAP_LINK) { + channel = PHY_MSE_CHANNEL_LINK; + } else { + ret = -EOPNOTSUPP; + goto unlock; + } + + ret = phydev->drv->get_mse_snapshot(phydev, channel, &snapshot); + if (ret) + goto unlock; + + data->mse_value = snapshot.average_mse; + data->mse_max = cap.max_average_mse; + data->mse_channel = channel; + +unlock: + mutex_unlock(&phydev->lock); + return ret; +} + +static bool linkstate_mse_critical_error(int err) +{ + return err < 0 && err != -EOPNOTSUPP && err != -ENETDOWN && + err != -EAGAIN; +} + +static bool linkstate_mse_valid(struct linkstate_reply_data *data) +{ + return data->mse_max > 0 && data->mse_value <= data->mse_max; +} + static int linkstate_get_link_ext_state(struct net_device *dev, struct linkstate_reply_data *data) { @@ -125,6 +194,10 @@ static int linkstate_prepare_data(const struct ethnl_req_info *req_base, goto out; data->sqi_max = ret; + ret = linkstate_get_mse(phydev, data); + if (linkstate_mse_critical_error(ret)) + goto out; + if (dev->flags & IFF_UP) { ret = linkstate_get_link_ext_state(dev, data); if (ret < 0 && ret != -EOPNOTSUPP && ret != -ENODATA) @@ -164,6 +237,15 @@ static int linkstate_reply_size(const struct ethnl_req_info *req_base, len += nla_total_size(sizeof(u32)); /* LINKSTATE_SQI_MAX */ } + if (linkstate_mse_valid(data)) { + /* LINKSTATE_MSE_VALUE */ + len += nla_total_size(sizeof(u64)); + /* LINKSTATE_MSE_MAX */ + len += nla_total_size(sizeof(u64)); + /* LINKSTATE_MSE_CHANNEL */ + len += nla_total_size(sizeof(u64)); + } + if (data->link_ext_state_provided) len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_STATE */ @@ -195,6 +277,18 @@ static int linkstate_fill_reply(struct sk_buff *skb, return -EMSGSIZE; } + if (linkstate_mse_valid(data)) { + if (nla_put_uint(skb, ETHTOOL_A_LINKSTATE_MSE_VALUE, + data->mse_value)) + return -EMSGSIZE; + if (nla_put_uint(skb, ETHTOOL_A_LINKSTATE_MSE_MAX, + data->mse_max)) + return -EMSGSIZE; + if (nla_put_uint(skb, ETHTOOL_A_LINKSTATE_MSE_CHANNEL, + data->mse_channel)) + return -EMSGSIZE; + } + if (data->link_ext_state_provided) { if (nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_STATE, data->ethtool_link_ext_state_info.link_ext_state)) -- 2.47.3 Implement the get_mse_capability() and get_mse_snapshot() PHY driver ops for KSZ9477-series integrated PHYs to demonstrate the new PHY MSE UAPI. These PHYs do not expose a documented direct MSE register, but the Signal Quality Indicator (SQI) registers are derived from the internal MSE computation. This hook maps SQI readings into the MSE interface so that tooling can retrieve the raw value together with metadata for correct interpretation in userspace. Behaviour: - For 1000BASE-T, report per-channel (A–D) values and support a WORST channel selector. - For 100BASE-TX, only LINK-wide measurements are available. - Report average MSE only, with a max scale based on KSZ9477_MMD_SQI_MASK and a fixed refresh rate of 2 µs. This mapping differs from the OPEN Alliance SQI definition, which assigns thresholds such as pre-fail indices; the MSE interface instead provides the raw measurement, leaving interpretation to userspace. Signed-off-by: Oleksij Rempel --- changes v6: - update comments - s/get_mse_config/get_mse_capability/ --- drivers/net/phy/micrel.c | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 79ce3eb6752b..08c2dbc933a0 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -2323,6 +2323,105 @@ 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, u32 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; @@ -6463,6 +6562,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); -- 2.47.3 Implement get_mse_capability() and get_mse_snapshot() for the DP83TD510E to expose its Mean Square Error (MSE) register via the new PHY MSE UAPI. The DP83TD510E does not document any peak MSE values; it only exposes a single average MSE register used internally to derive SQI. This implementation therefore advertises only PHY_MSE_CAP_AVG, along with LINK and channel-A selectors. Scaling is fixed to 0xFFFF, and the refresh interval/number of symbols are estimated from 10BASE-T1L symbol rate (7.5 MBd) and typical diagnostic intervals (~1 ms). For 10BASE-T1L deployments, SQI is a reliable indicator of link modulation quality once the link is established, but it does not indicate whether autonegotiation pulses will be correctly received in marginal conditions. MSE provides a direct measurement of slicer error rate that can be used to evaluate if autonegotiation is likely to succeed under a given cable length and condition. In practice, testing such scenarios often requires forcing a fixed-link setup to isolate MSE behaviour from the autonegotiation process. Signed-off-by: Oleksij Rempel --- drivers/net/phy/dp83td510.c | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index 23af1ac194fa..6875f418fa78 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -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,63 @@ 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, u32 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 +951,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, -- 2.47.3