* [RFC net-next v2 0/6] ethtool: Generic loopback support
@ 2026-03-08 12:40 Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
` (6 more replies)
0 siblings, 7 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Hi!
Background
==========
This is the v2 RFC of the CMIS loopback series, reworked based on
feedback from v1 [1].
The main change is that loopback is no longer bolted onto the existing
module interface. Instead, it gets its own netlink commands
(LOOPBACK_GET/SET) with a generic component/name/direction model that
can represent loopback points across the entire data path -- MODULE,
PHY, MAC, and PCS. This series wires up MODULE/CMIS as the first user;
the other component types return -EOPNOTSUPP for now.
The Common Management Interface Specification (CMIS) defines four
diagnostic loopback types, characterized by location (Host or Media
Side) and signal direction:
- Host Side Input (Rx->Tx) -- near-end
- Host Side Output (Tx->Rx) -- far-end
- Media Side Input (Rx->Tx) -- near-end
- Media Side Output (Tx->Rx) -- far-end
Support is detected via Page 13h Byte 128, and loopback is controlled
via Page 13h Bytes 180-183 (one byte per type, one bit per lane).
The CMIS helpers work entirely over get/set_module_eeprom_by_page, so
any driver that already has EEPROM page access gets module loopback
without new ethtool_ops or driver changes.
Implementation
==============
Patch 1/6 ethtool: Add loopback netlink UAPI definitions
Adds the YAML spec and generated UAPI header for the new
LOOPBACK_GET/SET commands. Each loopback entry carries a component
type, optional id, name string, supported directions bitmask, and
current direction.
Patch 2/6 ethtool: Add loopback GET/SET netlink implementation
Implements GET/SET dispatch in a new loopback.c. GET collects
entries from each subsystem. SET switches on the component and
calls the right handler per entry. No components are wired yet.
Patch 3/6 ethtool: add CMIS loopback helpers for module loopback control
Adds cmis_loopback.c with the MODULE component handlers. GET reads
Page 13h and appends one entry per supported loopback point
("cmis-host" and/or "cmis-media"). SET resolves name to control
byte indices and enforces mutual exclusivity -- switching directions
first disables the active one in a separate EEPROM write, then
enables the new one.
Patch 4/6 selftests: drv-net: Add loopback driver test
Adds loopback_drv.py with generic tests that work on any device
with module loopback support: enable/disable, direction switching,
idempotent enable, and rejection while interface is up.
Patch 5/6 netdevsim: Add module EEPROM simulation via debugfs
Adds get/set_module_eeprom_by_page to netdevsim, backed by a
256-page x 128-byte array exposed via debugfs. This enables
testing the CMIS loopback path without hardware.
Patch 6/6 selftests: drv-net: Add CMIS loopback netdevsim test
Adds loopback_nsim.py with netdevsim-specific tests that seed the
EEPROM via debugfs: capability reporting, EEPROM byte verification,
and error paths for unsupported or missing CMIS support.
Limitations
===========
Only MODULE/CMIS is wired up. PHY, MAC, and PCS loopback are defined
in the UAPI but not yet implemented.
No per-lane support -- loopback is all-or-nothing (0xFF/0x00) across
lanes.
Extending to PHY/MAC/PCS
=========================
PHY loopback can walk phy_link_topology to enumerate PHYs by phyindex
and call phy_loopback() directly. MAC and PCS loopback can route
through phylink via new mac_set_loopback()/pcs_set_loopback()
callbacks. Drivers that don't use phylink could add new ethtool_ops.
The dispatch framework already handles all component types.
Open questions
==============
- Is this the right extensibility model? I'd appreciate input from
other NIC vendors on whether component/name/direction is flexible
enough for their loopback implementations. Also, from the PHY/port
folks (Maxime, Russell)!
- The MODULE id field is currently unused. For multi-module setups it
could serve as a port selector. It could also help detect module
swaps -- a hash of the CMIS vendor serial number (Page 00h, Bytes
168-183), vendor name, and part number would give userspace a
stable identifier to verify the module hasn't changed since
loopback was configured. Worth adding now, or defer until there's a
concrete user?
- Are patches 5-6 (netdevsim EEPROM simulation + netdevsim-specific
tests) worth carrying? They drive the CMIS Page 13h registers from
debugfs, which gives good coverage without hardware, but it's
another netdevsim surface to maintain. If the consensus is that the
generic driver tests (patch 4) are sufficient, I'm happy to drop
them.
Related work
============
[1] CMIS loopback v1
https://lore.kernel.org/netdev/20260219130050.2390226-1-bjorn@kernel.org/
[2] New loopback modes
https://lore.kernel.org/netdev/20251024044849.1098222-1-hkelam@marvell.com/
[3] PHY loopback
https://lore.kernel.org/netdev/20240911212713.2178943-1-maxime.chevallier@bootlin.com/
[4] bnxt_en: add .set_module_eeprom_by_page() support
https://lore.kernel.org/netdev/20250310183129.3154117-8-michael.chan@broadcom.com/
Björn Töpel (6):
ethtool: Add loopback netlink UAPI definitions
ethtool: Add loopback GET/SET netlink implementation
ethtool: add CMIS loopback helpers for module loopback control
selftests: drv-net: Add loopback driver test
netdevsim: Add module EEPROM simulation via debugfs
selftests: drv-net: Add CMIS loopback netdevsim test
Documentation/netlink/specs/ethtool.yaml | 115 ++++++
drivers/net/netdevsim/ethtool.c | 79 ++++
drivers/net/netdevsim/netdevsim.h | 11 +
include/linux/ethtool.h | 28 ++
.../uapi/linux/ethtool_netlink_generated.h | 52 +++
net/ethtool/Makefile | 2 +-
net/ethtool/cmis_loopback.c | 338 ++++++++++++++++++
net/ethtool/loopback.c | 248 +++++++++++++
net/ethtool/netlink.c | 20 ++
net/ethtool/netlink.h | 8 +
.../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++++
.../selftests/drivers/net/hw/loopback_nsim.py | 249 +++++++++++++
12 files changed, 1376 insertions(+), 1 deletion(-)
create mode 100644 net/ethtool/cmis_loopback.c
create mode 100644 net/ethtool/loopback.c
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_nsim.py
base-commit: 0bcac7b11262557c990da1ac564d45777eb6b005
--
2.53.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-09 14:16 ` Maxime Chevallier
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
` (5 subsequent siblings)
6 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add the netlink YAML spec and auto-generated UAPI header for a unified
loopback interface covering MAC, PCS, PHY, and pluggable module
components.
Each loopback point is described by a nested entry attribute
containing:
- component where in the path (MAC, PCS, PHY, MODULE)
- name subsystem label, e.g. "cmis-host" or "cmis-media"
- id optional instance selector (e.g. PHY id, port id)
- supported bitmask of supported directions
- direction NEAR_END, FAR_END, or 0 (disabled)
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
Documentation/netlink/specs/ethtool.yaml | 115 ++++++++++++++++++
.../uapi/linux/ethtool_netlink_generated.h | 52 ++++++++
2 files changed, 167 insertions(+)
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 4707063af3b4..05ebad6ae4e0 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -211,6 +211,39 @@ definitions:
name: discard
value: 31
+ -
+ name: loopback-component
+ type: enum
+ doc: |
+ Loopback component. Identifies where in the network path the
+ loopback is applied.
+ entries:
+ -
+ name: mac
+ doc: MAC loopback
+ -
+ name: pcs
+ doc: PCS loopback
+ -
+ name: phy
+ doc: PHY loopback
+ -
+ name: module
+ doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
+ -
+ name: loopback-direction
+ type: flags
+ doc: |
+ Loopback direction flags. Used as a bitmask in supported, and as
+ a single value in direction.
+ entries:
+ -
+ name: near-end
+ doc: Near-end loopback; host-loop-host
+ -
+ name: far-end
+ doc: Far-end loopback; line-loop-line
+
attribute-sets:
-
name: header
@@ -1903,6 +1936,60 @@ attribute-sets:
name: link
type: nest
nested-attributes: mse-snapshot
+ -
+ name: loopback-entry
+ doc: Per-component loopback configuration entry.
+ attr-cnt-name: __ethtool-a-loopback-entry-cnt
+ attributes:
+ -
+ name: unspec
+ type: unused
+ value: 0
+ -
+ name: component
+ type: u32
+ enum: loopback-component
+ doc: Loopback component
+ -
+ name: id
+ type: u32
+ doc: |
+ Optional component instance identifier. Required for PHY,
+ optional for MODULE, omitted for MAC and PCS.
+ -
+ name: name
+ type: string
+ doc: |
+ Subsystem-specific name for the loopback point within the
+ component.
+ -
+ name: supported
+ type: u32
+ enum: loopback-direction
+ enum-as-flags: true
+ doc: Bitmask of supported loopback directions
+ -
+ name: direction
+ type: u32
+ enum: loopback-direction
+ doc: Current loopback direction, 0 means disabled
+ -
+ name: loopback
+ attr-cnt-name: __ethtool-a-loopback-cnt
+ attributes:
+ -
+ name: unspec
+ type: unused
+ value: 0
+ -
+ name: header
+ type: nest
+ nested-attributes: header
+ -
+ name: entry
+ type: nest
+ multi-attr: true
+ nested-attributes: loopback-entry
operations:
enum-model: directional
@@ -2855,6 +2942,34 @@ operations:
- worst-channel
- link
dump: *mse-get-op
+ -
+ name: loopback-get
+ doc: Get loopback configuration and capabilities.
+
+ attribute-set: loopback
+
+ do: &loopback-get-op
+ request:
+ attributes:
+ - header
+ reply:
+ attributes: &loopback
+ - header
+ - entry
+ dump: *loopback-get-op
+ -
+ name: loopback-set
+ doc: Set loopback configuration.
+
+ attribute-set: loopback
+
+ do:
+ request:
+ attributes: *loopback
+ -
+ name: loopback-ntf
+ doc: Notification for change in loopback configuration.
+ notify: loopback-get
mcast-groups:
list:
diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h
index 114b83017297..83fb17dd5daf 100644
--- a/include/uapi/linux/ethtool_netlink_generated.h
+++ b/include/uapi/linux/ethtool_netlink_generated.h
@@ -78,6 +78,33 @@ enum ethtool_pse_event {
ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,
};
+/**
+ * enum ethtool_loopback_component - Loopback component. Identifies where in
+ * the network path the loopback is applied.
+ * @ETHTOOL_LOOPBACK_COMPONENT_MAC: MAC loopback
+ * @ETHTOOL_LOOPBACK_COMPONENT_PCS: PCS loopback
+ * @ETHTOOL_LOOPBACK_COMPONENT_PHY: PHY loopback
+ * @ETHTOOL_LOOPBACK_COMPONENT_MODULE: Pluggable module (e.g. CMIS (Q)SFP)
+ * loopback
+ */
+enum ethtool_loopback_component {
+ ETHTOOL_LOOPBACK_COMPONENT_MAC,
+ ETHTOOL_LOOPBACK_COMPONENT_PCS,
+ ETHTOOL_LOOPBACK_COMPONENT_PHY,
+ ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+};
+
+/**
+ * enum ethtool_loopback_direction - Loopback direction flags. Used as a
+ * bitmask in supported, and as a single value in direction.
+ * @ETHTOOL_LOOPBACK_DIRECTION_NEAR_END: Near-end loopback; host-loop-host
+ * @ETHTOOL_LOOPBACK_DIRECTION_FAR_END: Far-end loopback; line-loop-line
+ */
+enum ethtool_loopback_direction {
+ ETHTOOL_LOOPBACK_DIRECTION_NEAR_END = 1,
+ ETHTOOL_LOOPBACK_DIRECTION_FAR_END = 2,
+};
+
enum {
ETHTOOL_A_HEADER_UNSPEC,
ETHTOOL_A_HEADER_DEV_INDEX,
@@ -838,6 +865,27 @@ enum {
ETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1)
};
+enum {
+ ETHTOOL_A_LOOPBACK_ENTRY_UNSPEC,
+ ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT,
+ ETHTOOL_A_LOOPBACK_ENTRY_ID,
+ ETHTOOL_A_LOOPBACK_ENTRY_NAME,
+ ETHTOOL_A_LOOPBACK_ENTRY_SUPPORTED,
+ ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION,
+
+ __ETHTOOL_A_LOOPBACK_ENTRY_CNT,
+ ETHTOOL_A_LOOPBACK_ENTRY_MAX = (__ETHTOOL_A_LOOPBACK_ENTRY_CNT - 1)
+};
+
+enum {
+ ETHTOOL_A_LOOPBACK_UNSPEC,
+ ETHTOOL_A_LOOPBACK_HEADER,
+ ETHTOOL_A_LOOPBACK_ENTRY,
+
+ __ETHTOOL_A_LOOPBACK_CNT,
+ ETHTOOL_A_LOOPBACK_MAX = (__ETHTOOL_A_LOOPBACK_CNT - 1)
+};
+
enum {
ETHTOOL_MSG_USER_NONE = 0,
ETHTOOL_MSG_STRSET_GET = 1,
@@ -891,6 +939,8 @@ enum {
ETHTOOL_MSG_RSS_CREATE_ACT,
ETHTOOL_MSG_RSS_DELETE_ACT,
ETHTOOL_MSG_MSE_GET,
+ ETHTOOL_MSG_LOOPBACK_GET,
+ ETHTOOL_MSG_LOOPBACK_SET,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -952,6 +1002,8 @@ enum {
ETHTOOL_MSG_RSS_CREATE_NTF,
ETHTOOL_MSG_RSS_DELETE_NTF,
ETHTOOL_MSG_MSE_GET_REPLY,
+ ETHTOOL_MSG_LOOPBACK_GET_REPLY,
+ ETHTOOL_MSG_LOOPBACK_NTF,
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-09 7:34 ` Maxime Chevallier
2026-03-08 12:40 ` [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control Björn Töpel
` (4 subsequent siblings)
6 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET,
ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using
the standard ethnl_request_ops infrastructure.
GET collects loopback entries from per-component helpers via
loopback_get_entries(). SET parses the nested entry attributes,
dispatches each to loopback_set_one(), and only sends a notification
when the state is changed.
No components are wired yet.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
include/linux/ethtool.h | 28 +++++
net/ethtool/Makefile | 2 +-
net/ethtool/loopback.c | 246 ++++++++++++++++++++++++++++++++++++++++
net/ethtool/netlink.c | 20 ++++
net/ethtool/netlink.h | 3 +
5 files changed, 298 insertions(+), 1 deletion(-)
create mode 100644 net/ethtool/loopback.c
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 83c375840835..b1ebfe22c355 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -846,6 +846,34 @@ void ethtool_mmsv_set_mm(struct ethtool_mmsv *mmsv, struct ethtool_mm_cfg *cfg);
void ethtool_mmsv_init(struct ethtool_mmsv *mmsv, struct net_device *dev,
const struct ethtool_mmsv_ops *ops);
+/**
+ * struct ethtool_loopback_entry - Per-component loopback configuration
+ * @id: Optional component instance identifier, 0 means not specified
+ * @supported: Bitmask of supported directions
+ * @component: Loopback component
+ * @direction: Current loopback direction, 0 means disabled
+ * @name: Subsystem-specific name for the loopback point
+ */
+struct ethtool_loopback_entry {
+ enum ethtool_loopback_component component;
+ u32 id;
+ u32 supported;
+ u32 direction;
+ char name[ETH_GSTRING_LEN];
+};
+
+#define ETHTOOL_LOOPBACK_MAX_ENTRIES 16
+
+/**
+ * struct ethtool_loopback_cfg - Loopback configuration
+ * @entries: Array of per-component loopback configurations
+ * @n_entries: Number of valid entries in the array
+ */
+struct ethtool_loopback_cfg {
+ struct ethtool_loopback_entry entries[ETHTOOL_LOOPBACK_MAX_ENTRIES];
+ u32 n_entries;
+};
+
/**
* struct ethtool_rxfh_param - RXFH (RSS) parameters
* @hfunc: Defines the current RSS hash function used by HW (or to be set to).
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 629c10916670..ef534b55d724 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 mse.o
+ phy.o tsconfig.o mse.o loopback.o
diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c
new file mode 100644
index 000000000000..1c6d27857f8a
--- /dev/null
+++ b/net/ethtool/loopback.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "netlink.h"
+#include "common.h"
+
+struct loopback_req_info {
+ struct ethnl_req_info base;
+};
+
+struct loopback_reply_data {
+ struct ethnl_reply_data base;
+ struct ethtool_loopback_cfg cfg;
+};
+
+#define LOOPBACK_REPDATA(__reply_base) \
+ container_of(__reply_base, struct loopback_reply_data, base)
+
+/* GET */
+
+static const struct nla_policy
+ethnl_loopback_entry_policy[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1] = {
+ [ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT] =
+ NLA_POLICY_MAX(NLA_U32, ETHTOOL_LOOPBACK_COMPONENT_MODULE),
+ [ETHTOOL_A_LOOPBACK_ENTRY_ID] =
+ NLA_POLICY_MIN(NLA_U32, 1),
+ [ETHTOOL_A_LOOPBACK_ENTRY_NAME] =
+ { .type = NLA_NUL_STRING, .len = ETH_GSTRING_LEN - 1 },
+ [ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION] =
+ NLA_POLICY_MASK(NLA_U32, ETHTOOL_LOOPBACK_DIRECTION_NEAR_END |
+ ETHTOOL_LOOPBACK_DIRECTION_FAR_END),
+};
+
+const struct nla_policy ethnl_loopback_get_policy[] = {
+ [ETHTOOL_A_LOOPBACK_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+};
+
+static int loopback_get_entries(struct net_device *dev,
+ struct ethtool_loopback_cfg *cfg)
+{
+ return 0;
+}
+
+static int loopback_prepare_data(const struct ethnl_req_info *req_base,
+ struct ethnl_reply_data *reply_base,
+ const struct genl_info *info)
+{
+ struct loopback_reply_data *data = LOOPBACK_REPDATA(reply_base);
+ struct net_device *dev = reply_base->dev;
+ int ret;
+
+ ret = ethnl_ops_begin(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = loopback_get_entries(dev, &data->cfg);
+
+ ethnl_ops_complete(dev);
+
+ return ret;
+}
+
+static int loopback_reply_size(const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct loopback_reply_data *data = LOOPBACK_REPDATA(reply_base);
+ int entry_size;
+
+ /* Per-entry: nest + component + id + name + supported + direction */
+ entry_size = nla_total_size(0) + /* nest */
+ nla_total_size(sizeof(u32)) + /* component */
+ nla_total_size(sizeof(u32)) + /* id */
+ nla_total_size(sizeof(u32)) + /* supported */
+ nla_total_size(sizeof(u32)) + /* direction */
+ nla_total_size(ETH_GSTRING_LEN); /* name */
+
+ return data->cfg.n_entries * entry_size;
+}
+
+static int loopback_fill_entry(struct sk_buff *skb,
+ const struct ethtool_loopback_entry *entry)
+{
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, ETHTOOL_A_LOOPBACK_ENTRY);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT,
+ entry->component))
+ goto err_cancel;
+
+ if (entry->id &&
+ nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_ID, entry->id))
+ goto err_cancel;
+
+ if (nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_SUPPORTED,
+ entry->supported) ||
+ nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION,
+ entry->direction) ||
+ nla_put_string(skb, ETHTOOL_A_LOOPBACK_ENTRY_NAME,
+ entry->name))
+ goto err_cancel;
+
+ nla_nest_end(skb, nest);
+ return 0;
+
+err_cancel:
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+}
+
+static int loopback_fill_reply(struct sk_buff *skb,
+ const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct loopback_reply_data *data = LOOPBACK_REPDATA(reply_base);
+ const struct ethtool_loopback_cfg *cfg = &data->cfg;
+ u32 i;
+
+ for (i = 0; i < cfg->n_entries; i++) {
+ int ret = loopback_fill_entry(skb, &cfg->entries[i]);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* SET */
+
+const struct nla_policy ethnl_loopback_set_policy[ETHTOOL_A_LOOPBACK_ENTRY + 1] = {
+ [ETHTOOL_A_LOOPBACK_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+ [ETHTOOL_A_LOOPBACK_ENTRY] = NLA_POLICY_NESTED(ethnl_loopback_entry_policy),
+};
+
+static int loopback_parse_entry(struct nlattr *attr,
+ struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested(tb, ETHTOOL_A_LOOPBACK_ENTRY_MAX, attr,
+ ethnl_loopback_entry_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]) {
+ NL_SET_ERR_MSG_ATTR(extack, attr,
+ "loopback component is required");
+ return -EINVAL;
+ }
+
+ entry->component = nla_get_u32(tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]);
+
+ if (tb[ETHTOOL_A_LOOPBACK_ENTRY_ID])
+ entry->id = nla_get_u32(tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]);
+
+ if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME]) {
+ NL_SET_ERR_MSG_ATTR(extack, attr,
+ "loopback name is required");
+ return -EINVAL;
+ }
+ nla_strscpy(entry->name, tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME],
+ sizeof(entry->name));
+
+ if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION]) {
+ NL_SET_ERR_MSG_ATTR(extack, attr,
+ "loopback direction is required");
+ return -EINVAL;
+ }
+
+ entry->direction = nla_get_u32(tb[ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION]);
+
+ return 0;
+}
+
+static int loopback_set_one(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ switch (entry->component) {
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ethnl_set_loopback(struct ethnl_req_info *req_info,
+ struct genl_info *info)
+{
+ struct net_device *dev = req_info->dev;
+ struct ethtool_loopback_cfg cfg = {};
+ int rem, ret, mod = 0;
+ struct nlattr *attr;
+ u32 i;
+
+ nla_for_each_attr(attr, genlmsg_data(info->genlhdr),
+ genlmsg_len(info->genlhdr), rem) {
+ if (nla_type(attr) != ETHTOOL_A_LOOPBACK_ENTRY)
+ continue;
+
+ if (cfg.n_entries >= ETHTOOL_LOOPBACK_MAX_ENTRIES) {
+ NL_SET_ERR_MSG(info->extack,
+ "too many loopback entries");
+ return -EINVAL;
+ }
+
+ ret = loopback_parse_entry(attr, &cfg.entries[cfg.n_entries],
+ info->extack);
+ if (ret < 0)
+ return ret;
+
+ cfg.n_entries++;
+ }
+
+ if (!cfg.n_entries) {
+ NL_SET_ERR_MSG(info->extack, "no loopback entries specified");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < cfg.n_entries; i++) {
+ ret = loopback_set_one(dev, &cfg.entries[i], info->extack);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ mod = 1;
+ }
+
+ return mod;
+}
+
+const struct ethnl_request_ops ethnl_loopback_request_ops = {
+ .request_cmd = ETHTOOL_MSG_LOOPBACK_GET,
+ .reply_cmd = ETHTOOL_MSG_LOOPBACK_GET_REPLY,
+ .hdr_attr = ETHTOOL_A_LOOPBACK_HEADER,
+ .req_info_size = sizeof(struct loopback_req_info),
+ .reply_data_size = sizeof(struct loopback_reply_data),
+
+ .prepare_data = loopback_prepare_data,
+ .reply_size = loopback_reply_size,
+ .fill_reply = loopback_fill_reply,
+
+ .set = ethnl_set_loopback,
+ .set_ntf_cmd = ETHTOOL_MSG_LOOPBACK_NTF,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6e5f0f4f815a..c438828ea072 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -421,6 +421,8 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops,
[ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops,
[ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops,
+ [ETHTOOL_MSG_LOOPBACK_GET] = ðnl_loopback_request_ops,
+ [ETHTOOL_MSG_LOOPBACK_SET] = ðnl_loopback_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -962,6 +964,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_MM_NTF] = ðnl_mm_request_ops,
[ETHTOOL_MSG_RSS_NTF] = ðnl_rss_request_ops,
[ETHTOOL_MSG_RSS_CREATE_NTF] = ðnl_rss_request_ops,
+ [ETHTOOL_MSG_LOOPBACK_NTF] = ðnl_loopback_request_ops,
};
/* default notification handler */
@@ -1070,6 +1073,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_MM_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_RSS_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_RSS_CREATE_NTF] = ethnl_default_notify,
+ [ETHTOOL_MSG_LOOPBACK_NTF] = ethnl_default_notify,
};
void ethnl_notify(struct net_device *dev, unsigned int cmd,
@@ -1544,6 +1548,22 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_mse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
},
+ {
+ .cmd = ETHTOOL_MSG_LOOPBACK_GET,
+ .doit = ethnl_default_doit,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
+ .policy = ethnl_loopback_get_policy,
+ .maxattr = ARRAY_SIZE(ethnl_loopback_get_policy) - 1,
+ },
+ {
+ .cmd = ETHTOOL_MSG_LOOPBACK_SET,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_default_set_doit,
+ .policy = ethnl_loopback_set_policy,
+ .maxattr = ARRAY_SIZE(ethnl_loopback_set_policy) - 1,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 89010eaa67df..5660ce494916 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -443,6 +443,7 @@ 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 ethnl_request_ops ethnl_loopback_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];
@@ -499,6 +500,8 @@ 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];
+extern const struct nla_policy ethnl_loopback_get_policy[ETHTOOL_A_LOOPBACK_HEADER + 1];
+extern const struct nla_policy ethnl_loopback_set_policy[ETHTOOL_A_LOOPBACK_ENTRY + 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);
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test Björn Töpel
` (3 subsequent siblings)
6 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add CMIS loopback functions and wire them into loopback.c for the
MODULE component:
- ethtool_cmis_get_loopback(): reads Page 13h capabilities and
current state, appends one entry per supported loopback point
("cmis-host" and/or "cmis-media").
- ethtool_cmis_set_loopback_one(): resolves name to a pair of control
byte indices, validates direction, and writes the Page 13h control
bytes (0xFF = all lanes on, 0x00 = off).
Directions are mutually exclusive: switching from near-end to far-end
first disables the active direction in a separate EEPROM write, then
enables the new one. Requesting multiple direction flags is rejected.
CMIS register mapping (Page 13h, Bytes 180-183):
- MODULE, "cmis-host", near-end -> Host Side Input (Byte 183)
- MODULE, "cmis-host", far-end -> Host Side Output (Byte 182)
- MODULE, "cmis-media", near-end -> Media Side Input (Byte 181)
- MODULE, "cmis-media", far-end -> Media Side Output (Byte 180)
The helpers work entirely over get/set_module_eeprom_by_page, so any
driver with EEPROM page access gets module loopback without new
ethtool_ops or driver changes. SET is rejected when firmware flashing
is in progress or the interface is UP.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
net/ethtool/Makefile | 2 +-
net/ethtool/cmis_loopback.c | 338 ++++++++++++++++++++++++++++++++++++
net/ethtool/loopback.c | 4 +-
net/ethtool/netlink.h | 5 +
4 files changed, 347 insertions(+), 2 deletions(-)
create mode 100644 net/ethtool/cmis_loopback.c
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index ef534b55d724..2f821c7875e1 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 mse.o loopback.o
+ phy.o tsconfig.o mse.o loopback.o cmis_loopback.o
diff --git a/net/ethtool/cmis_loopback.c b/net/ethtool/cmis_loopback.c
new file mode 100644
index 000000000000..2114c85f507f
--- /dev/null
+++ b/net/ethtool/cmis_loopback.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/* CMIS loopback helpers for drivers implementing ethtool
+ * get/set_loopback.
+ *
+ * Maps the generic ethtool loopback model to CMIS Page 13h registers
+ * (CMIS 5.3, Table 8-128).
+ *
+ * Capabilities are read from Page 13h Byte 128, with Page 13h
+ * availability checked via Page 01h Byte 142 bit 5.
+ */
+
+#include <linux/ethtool.h>
+#include <linux/sfp.h>
+
+#include "common.h"
+#include "module_fw.h"
+#include "cmis.h"
+
+/* CMIS Page 00h, Byte 0: Physical module identifier */
+#define CMIS_PHYS_ID_PAGE 0x00
+#define CMIS_PHYS_ID_OFFSET 0x00
+
+/* CMIS Page 01h, Byte 142: Diagnostic Pages Support */
+#define CMIS_DIAG_SUPPORT_PAGE 0x01
+#define CMIS_DIAG_SUPPORT_OFFSET 0x8E
+#define CMIS_DIAG_PAGE13_BIT BIT(5)
+
+/* CMIS Page 13h, Byte 128: Loopback Capability Advertisement */
+#define CMIS_LB_CAPS_PAGE 0x13
+#define CMIS_LB_CAPS_OFFSET 0x80
+#define CMIS_LB_CAP_MEDIA_OUTPUT BIT(0)
+#define CMIS_LB_CAP_MEDIA_INPUT BIT(1)
+#define CMIS_LB_CAP_HOST_OUTPUT BIT(2)
+#define CMIS_LB_CAP_HOST_INPUT BIT(3)
+
+/* CMIS Page 13h, Bytes 180-183: Per-Lane Loopback Control
+ * Byte 180 (0xB4): Media Side Output -> MODULE, "cmis-media", far-end
+ * Byte 181 (0xB5): Media Side Input -> MODULE, "cmis-media", near-end
+ * Byte 182 (0xB6): Host Side Output -> MODULE, "cmis-host", far-end
+ * Byte 183 (0xB7): Host Side Input -> MODULE, "cmis-host", near-end
+ */
+#define CMIS_LB_CTRL_PAGE 0x13
+#define CMIS_LB_CTRL_OFFSET 0xB4
+#define CMIS_LB_CTRL_LEN 4
+#define CMIS_LB_CTRL_IDX_MEDIA_OUTPUT 0
+#define CMIS_LB_CTRL_IDX_MEDIA_INPUT 1
+#define CMIS_LB_CTRL_IDX_HOST_OUTPUT 2
+#define CMIS_LB_CTRL_IDX_HOST_INPUT 3
+
+#define CMIS_LB_NAME_HOST "cmis-host"
+#define CMIS_LB_NAME_MEDIA "cmis-media"
+
+static bool cmis_is_module(u8 phys_id)
+{
+ switch (phys_id) {
+ case SFF8024_ID_QSFP_DD:
+ case SFF8024_ID_OSFP:
+ case SFF8024_ID_DSFP:
+ case SFF8024_ID_QSFP_PLUS_CMIS:
+ case SFF8024_ID_SFP_DD_CMIS:
+ case SFF8024_ID_SFP_PLUS_CMIS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * cmis_loopback_caps - Read CMIS loopback capability mask
+ * @dev: Network device
+ *
+ * Return: >0 capability bitmask, 0 if not a CMIS module or no Page
+ * 13h, negative errno on failure.
+ */
+static int cmis_loopback_caps(struct net_device *dev)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_module_eeprom page = {};
+ int ret;
+ u8 val;
+
+ if (!ops->get_module_eeprom_by_page)
+ return 0;
+
+ /* Read physical identifier */
+ ethtool_cmis_page_init(&page, CMIS_PHYS_ID_PAGE,
+ CMIS_PHYS_ID_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+ if (!cmis_is_module(val))
+ return 0;
+
+ /* Check Page 13h availability */
+ ethtool_cmis_page_init(&page, CMIS_DIAG_SUPPORT_PAGE,
+ CMIS_DIAG_SUPPORT_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+ if (!(val & CMIS_DIAG_PAGE13_BIT))
+ return 0;
+
+ /* Read capability byte */
+ ethtool_cmis_page_init(&page, CMIS_LB_CAPS_PAGE,
+ CMIS_LB_CAPS_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ return val & (CMIS_LB_CAP_MEDIA_OUTPUT | CMIS_LB_CAP_MEDIA_INPUT |
+ CMIS_LB_CAP_HOST_OUTPUT | CMIS_LB_CAP_HOST_INPUT);
+}
+
+/**
+ * ethtool_cmis_get_loopback - Append CMIS module loopback entries to cfg
+ * @dev: Network device with get_module_eeprom_by_page support
+ * @cfg: Loopback configuration; MODULE entries are appended
+ *
+ * Reads CMIS module capabilities and current loopback state from Page
+ * 13h, then appends one entry for each supported loopback point.
+ * Returns 0 without adding entries if the module is not CMIS or does
+ * not advertise loopback support.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ struct ethtool_loopback_cfg *cfg)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_module_eeprom page = {};
+ struct ethtool_loopback_entry host = {
+ .component = ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+ .name = CMIS_LB_NAME_HOST,
+ };
+ struct ethtool_loopback_entry media = {
+ .component = ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+ .name = CMIS_LB_NAME_MEDIA,
+ };
+ int caps, ret, h = 0, m = 0;
+ u8 ctrl[CMIS_LB_CTRL_LEN];
+
+ if (dev->ethtool->module_fw_flash_in_progress)
+ return -EBUSY;
+
+ caps = cmis_loopback_caps(dev);
+ if (caps <= 0)
+ return caps;
+
+ /* Read all four control bytes in one access */
+ ethtool_cmis_page_init(&page, CMIS_LB_CTRL_PAGE,
+ CMIS_LB_CTRL_OFFSET, sizeof(ctrl));
+ page.data = ctrl;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (caps & CMIS_LB_CAP_HOST_INPUT) {
+ h = 1;
+ host.supported |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_INPUT])
+ host.direction |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ }
+ if (caps & CMIS_LB_CAP_HOST_OUTPUT) {
+ h = 1;
+ host.supported |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_OUTPUT])
+ host.direction |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_INPUT) {
+ m = 1;
+ media.supported |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_INPUT])
+ media.direction |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_OUTPUT) {
+ m = 1;
+ media.supported |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_OUTPUT])
+ media.direction |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ }
+
+ if (cfg->n_entries + h + m > ETHTOOL_LOOPBACK_MAX_ENTRIES)
+ return -ENOMEM;
+
+ if (h) {
+ memcpy(&cfg->entries[cfg->n_entries], &host, sizeof(host));
+ cfg->n_entries++;
+ }
+
+ if (m) {
+ memcpy(&cfg->entries[cfg->n_entries], &media, sizeof(media));
+ cfg->n_entries++;
+ }
+
+ return 0;
+}
+
+/**
+ * ethtool_cmis_set_loopback_one - Apply one MODULE loopback entry to CMIS
+ * @dev: Network device with get/set_module_eeprom_by_page support
+ * @entry: Loopback entry to apply (must be MODULE component)
+ * @extack: Netlink extended ack for error reporting
+ *
+ * Matches the entry against CMIS loopback points by name and
+ * direction, then reads, modifies, and writes the corresponding Page
+ * 13h control byte (0xFF for all-lanes enable, 0x00 for disable).
+ *
+ * When disabling (direction == 0), all loopback points matching the
+ * name are disabled regardless of their direction. When enabling,
+ * only the specific direction is activated.
+ *
+ * Return: 1 if hardware state changed, 0 if already in requested state,
+ * negative errno on failure.
+ */
+int ethtool_cmis_set_loopback_one(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ struct ethtool_module_eeprom page = {};
+ u8 ctrl[CMIS_LB_CTRL_LEN];
+ int near_idx, far_idx;
+ u8 near_cap, far_cap;
+ bool mod = false;
+ int caps, ret;
+
+ if (!dev->ethtool_ops->set_module_eeprom_by_page) {
+ NL_SET_ERR_MSG(extack,
+ "Module EEPROM write access not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (dev->ethtool->module_fw_flash_in_progress) {
+ NL_SET_ERR_MSG(extack,
+ "Module firmware flashing is in progress");
+ return -EBUSY;
+ }
+
+ if (dev->flags & IFF_UP) {
+ NL_SET_ERR_MSG(extack,
+ "Netdevice is up, module loopback change not permitted");
+ return -EBUSY;
+ }
+
+ if (entry->direction && !is_power_of_2(entry->direction)) {
+ NL_SET_ERR_MSG(extack,
+ "Only one loopback direction may be enabled at a time");
+ return -EINVAL;
+ }
+
+ if (strcmp(entry->name, CMIS_LB_NAME_HOST) == 0) {
+ near_idx = CMIS_LB_CTRL_IDX_HOST_INPUT;
+ far_idx = CMIS_LB_CTRL_IDX_HOST_OUTPUT;
+ near_cap = CMIS_LB_CAP_HOST_INPUT;
+ far_cap = CMIS_LB_CAP_HOST_OUTPUT;
+ } else if (strcmp(entry->name, CMIS_LB_NAME_MEDIA) == 0) {
+ near_idx = CMIS_LB_CTRL_IDX_MEDIA_INPUT;
+ far_idx = CMIS_LB_CTRL_IDX_MEDIA_OUTPUT;
+ near_cap = CMIS_LB_CAP_MEDIA_INPUT;
+ far_cap = CMIS_LB_CAP_MEDIA_OUTPUT;
+ } else {
+ NL_SET_ERR_MSG(extack, "Unknown CMIS loopback name");
+ return -EINVAL;
+ }
+
+ caps = cmis_loopback_caps(dev);
+ if (caps < 0)
+ return caps;
+ if (!caps) {
+ NL_SET_ERR_MSG(extack, "Module does not support CMIS loopback");
+ return -EOPNOTSUPP;
+ }
+
+ /* Read current control bytes */
+ ethtool_cmis_page_init(&page, CMIS_LB_CTRL_PAGE,
+ CMIS_LB_CTRL_OFFSET, sizeof(ctrl));
+ page.data = ctrl;
+ ret = dev->ethtool_ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (!entry->direction) {
+ /* Disable both directions */
+ if (ctrl[near_idx]) {
+ ctrl[near_idx] = 0x00;
+ mod = true;
+ }
+ if (ctrl[far_idx]) {
+ ctrl[far_idx] = 0x00;
+ mod = true;
+ }
+ } else {
+ int enable_idx, disable_idx;
+ u8 enable_cap;
+
+ if (entry->direction & ETHTOOL_LOOPBACK_DIRECTION_NEAR_END) {
+ enable_idx = near_idx;
+ enable_cap = near_cap;
+ disable_idx = far_idx;
+ } else {
+ enable_idx = far_idx;
+ enable_cap = far_cap;
+ disable_idx = near_idx;
+ }
+
+ if (!(caps & enable_cap)) {
+ NL_SET_ERR_MSG(extack,
+ "Loopback mode not supported by module");
+ return -EOPNOTSUPP;
+ }
+
+ /* Disable opposite direction first (mutual exclusivity) */
+ if (ctrl[disable_idx]) {
+ ctrl[disable_idx] = 0x00;
+ ret = dev->ethtool_ops->set_module_eeprom_by_page(dev,
+ &page,
+ extack);
+ if (ret < 0)
+ return ret;
+ mod = true;
+ }
+
+ if (ctrl[enable_idx] != 0xFF) {
+ ctrl[enable_idx] = 0xFF;
+ mod = true;
+ }
+ }
+
+ if (!mod)
+ return 0;
+
+ ret = dev->ethtool_ops->set_module_eeprom_by_page(dev, &page, extack);
+
+ return ret < 0 ? ret : 1;
+}
diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c
index 1c6d27857f8a..8a6f14f4b8cb 100644
--- a/net/ethtool/loopback.c
+++ b/net/ethtool/loopback.c
@@ -37,7 +37,7 @@ const struct nla_policy ethnl_loopback_get_policy[] = {
static int loopback_get_entries(struct net_device *dev,
struct ethtool_loopback_cfg *cfg)
{
- return 0;
+ return ethtool_cmis_get_loopback(dev, cfg);
}
static int loopback_prepare_data(const struct ethnl_req_info *req_base,
@@ -181,6 +181,8 @@ static int loopback_set_one(struct net_device *dev,
struct netlink_ext_ack *extack)
{
switch (entry->component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
+ return ethtool_cmis_set_loopback_one(dev, entry, extack);
default:
return -EOPNOTSUPP;
}
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 5660ce494916..707363462c12 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -517,6 +517,11 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_done(struct netlink_callback *cb);
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ struct ethtool_loopback_cfg *cfg);
+int ethtool_cmis_set_loopback_one(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack);
extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
` (2 preceding siblings ...)
2026-03-08 12:40 ` [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
` (2 subsequent siblings)
6 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add a selftest for the ethtool loopback UAPI exercising module
loopback via the loopback GET/SET netlink commands.
Works on any device that reports module loopback entries. Tests cover
enable near-end and far-end, disable, direction switching (mutual
exclusivity), idempotent enable, and rejection while interface is up.
Devices without module loopback support are skipped.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py
diff --git a/tools/testing/selftests/drivers/net/hw/loopback_drv.py b/tools/testing/selftests/drivers/net/hw/loopback_drv.py
new file mode 100755
index 000000000000..ab105664e07e
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/loopback_drv.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Tests for ethtool loopback GET/SET with CMIS modules.
+
+Works on any device that reports module loopback entries. On devices
+without CMIS loopback support, tests are skipped.
+"""
+
+import errno
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import KsftSkipEx, KsftFailEx, ksft_disruptive
+from lib.py import EthtoolFamily, NlError
+from lib.py import NetDrvEnv, ip, defer
+
+# Direction flags as YNL returns them (sets of flag name strings)
+DIR_NONE = set()
+DIR_NEAR_END = {'near-end'}
+DIR_FAR_END = {'far-end'}
+
+
+def _get_loopback(cfg):
+ """GET loopback and return the list of entries."""
+ result = cfg.ethnl.loopback_get({
+ 'header': {'dev-index': cfg.ifindex}
+ })
+ return result.get('entry', [])
+
+
+def _set_loopback(cfg, component, name, direction):
+ """SET loopback for a single entry."""
+ cfg.ethnl.loopback_set({
+ 'header': {'dev-index': cfg.ifindex},
+ 'entry': [{
+ 'component': component,
+ 'name': name,
+ 'direction': direction,
+ }]
+ })
+
+
+def _require_module_entries(cfg):
+ """Return module loopback entries, skip if none available."""
+ try:
+ entries = _get_loopback(cfg)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("Device does not support loopback")
+ raise
+ mod_entries = [e for e in entries if e['component'] == 'module']
+ if not mod_entries:
+ raise KsftSkipEx("No module loopback entries")
+ return mod_entries
+
+
+@ksft_disruptive
+def test_set_near_end(cfg):
+ """SET a module entry to near-end and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'near-end' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END)
+
+
+@ksft_disruptive
+def test_set_far_end(cfg):
+ """SET a module entry to far-end and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ far = [e for e in mod_entries
+ if 'far-end' in e['supported']]
+ if not far:
+ raise KsftSkipEx("No far-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = far[0]
+ _set_loopback(cfg, 'module', target['name'], 'far-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'far-end' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_FAR_END)
+
+
+@ksft_disruptive
+def test_set_disable(cfg):
+ """Enable then disable loopback and verify."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ # Disable
+ _set_loopback(cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_NONE,
+ "Direction should be off after disable")
+
+
+@ksft_disruptive
+def test_set_direction_switch(cfg):
+ """Enable near-end, then switch to far-end. The kernel must disable
+ near-end before enabling far-end (mutual exclusivity).
+ """
+ mod_entries = _require_module_entries(cfg)
+
+ both = [e for e in mod_entries
+ if 'near-end' in e['supported'] and 'far-end' in e['supported']]
+ if not both:
+ raise KsftSkipEx("No entry with both near-end and far-end support")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = both[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END)
+
+ # Switch to far-end
+ _set_loopback(cfg, 'module', target['name'], 'far-end')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_FAR_END,
+ "Should have switched to far-end")
+
+
+@ksft_disruptive
+def test_set_idempotent(cfg):
+ """Enable the same direction twice. Second call should not fail."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ # Second enable of the same direction should succeed
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'near-end' in e['supported']]
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END,
+ "Direction should still be near-end")
+
+
+@ksft_disruptive
+def test_set_while_up(cfg):
+ """SET while interface is UP should fail."""
+ mod_entries = _require_module_entries(cfg)
+
+ target = mod_entries[0]
+ direction = 'near-end'
+ if direction not in target['supported']:
+ direction = 'far-end'
+
+ try:
+ _set_loopback(cfg, 'module', target['name'], direction)
+ raise KsftFailEx("Should have rejected SET while interface is up")
+ except NlError as e:
+ ksft_eq(e.error, errno.EBUSY,
+ "Expected EBUSY when interface is up")
+
+
+def main() -> None:
+ with NetDrvEnv(__file__, nsim_test=False) as cfg:
+ cfg.ethnl = EthtoolFamily()
+
+ ksft_run([
+ test_set_near_end,
+ test_set_far_end,
+ test_set_disable,
+ test_set_direction_switch,
+ test_set_idempotent,
+ test_set_while_up,
+ ], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
` (3 preceding siblings ...)
2026-03-08 12:40 ` [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
6 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add get/set_module_eeprom_by_page ethtool ops to netdevsim, enabling
testing of kernel features that depend on module EEPROM access (e.g.
CMIS loopback) without real hardware.
The EEPROM is backed by a 256-page x 128-byte array exposed as binary
debugfs files under ports/<N>/ethtool/module/pages/{0..255}. Offsets
0-127 map to page 0 (lower memory), 128-255 to the requested page's
upper memory, following the CMIS layout. Error injection via get_err
and set_err follows the existing netdevsim pattern.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
drivers/net/netdevsim/ethtool.c | 79 +++++++++++++++++++++++++++++++
drivers/net/netdevsim/netdevsim.h | 11 +++++
2 files changed, 90 insertions(+)
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 36a201533aae..2145ccc8a9bd 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -195,6 +195,67 @@ nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats,
values[2].per_lane[3] = 0;
}
+static u8 *nsim_module_eeprom_ptr(struct netdevsim *ns,
+ const struct ethtool_module_eeprom *page_data, u32 *len)
+{
+ u32 offset;
+ u8 page;
+
+ if (page_data->offset < NSIM_MODULE_EEPROM_PAGE_LEN) {
+ page = 0;
+ offset = page_data->offset;
+ } else {
+ page = page_data->page;
+ offset = page_data->offset - NSIM_MODULE_EEPROM_PAGE_LEN;
+ }
+
+ if (page >= NSIM_MODULE_EEPROM_PAGES)
+ return NULL;
+
+ *len = min_t(u32, page_data->length, NSIM_MODULE_EEPROM_PAGE_LEN - offset);
+ return ns->ethtool.module.pages[page] + offset;
+}
+
+static int nsim_get_module_eeprom_by_page(struct net_device *dev,
+ const struct ethtool_module_eeprom *page_data,
+ struct netlink_ext_ack *extack)
+{
+ struct netdevsim *ns = netdev_priv(dev);
+ u32 len;
+ u8 *ptr;
+
+ if (ns->ethtool.module.get_err)
+ return -ns->ethtool.module.get_err;
+
+ ptr = nsim_module_eeprom_ptr(ns, page_data, &len);
+ if (!ptr)
+ return -EINVAL;
+
+ memcpy(page_data->data, ptr, len);
+
+ return len;
+}
+
+static int nsim_set_module_eeprom_by_page(struct net_device *dev,
+ const struct ethtool_module_eeprom *page_data,
+ struct netlink_ext_ack *extack)
+{
+ struct netdevsim *ns = netdev_priv(dev);
+ u32 len;
+ u8 *ptr;
+
+ if (ns->ethtool.module.set_err)
+ return -ns->ethtool.module.set_err;
+
+ ptr = nsim_module_eeprom_ptr(ns, page_data, &len);
+ if (!ptr)
+ return -EINVAL;
+
+ memcpy(ptr, page_data->data, len);
+
+ return 0;
+}
+
static int nsim_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info)
{
@@ -222,6 +283,8 @@ static const struct ethtool_ops nsim_ethtool_ops = {
.set_fecparam = nsim_set_fecparam,
.get_fec_stats = nsim_get_fec_stats,
.get_ts_info = nsim_get_ts_info,
+ .get_module_eeprom_by_page = nsim_get_module_eeprom_by_page,
+ .set_module_eeprom_by_page = nsim_set_module_eeprom_by_page,
};
static void nsim_ethtool_ring_init(struct netdevsim *ns)
@@ -237,6 +300,7 @@ static void nsim_ethtool_ring_init(struct netdevsim *ns)
void nsim_ethtool_init(struct netdevsim *ns)
{
struct dentry *ethtool, *dir;
+ int i;
ns->netdev->ethtool_ops = &nsim_ethtool_ops;
@@ -270,4 +334,19 @@ void nsim_ethtool_init(struct netdevsim *ns)
&ns->ethtool.ring.rx_mini_max_pending);
debugfs_create_u32("tx_max_pending", 0600, dir,
&ns->ethtool.ring.tx_max_pending);
+
+ dir = debugfs_create_dir("module", ethtool);
+ debugfs_create_u32("get_err", 0600, dir, &ns->ethtool.module.get_err);
+ debugfs_create_u32("set_err", 0600, dir, &ns->ethtool.module.set_err);
+
+ dir = debugfs_create_dir("pages", dir);
+ for (i = 0; i < NSIM_MODULE_EEPROM_PAGES; i++) {
+ char name[8];
+
+ ns->ethtool.module.page_blobs[i].data = ns->ethtool.module.pages[i];
+ ns->ethtool.module.page_blobs[i].size = NSIM_MODULE_EEPROM_PAGE_LEN;
+
+ snprintf(name, sizeof(name), "%u", i);
+ debugfs_create_blob(name, 0600, dir, &ns->ethtool.module.page_blobs[i]);
+ }
}
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index f767fc8a7505..965d0aa0940b 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -82,6 +82,16 @@ struct nsim_ethtool_pauseparam {
bool report_stats_tx;
};
+#define NSIM_MODULE_EEPROM_PAGES 256
+#define NSIM_MODULE_EEPROM_PAGE_LEN 128
+
+struct nsim_ethtool_module {
+ u32 get_err;
+ u32 set_err;
+ u8 pages[NSIM_MODULE_EEPROM_PAGES][NSIM_MODULE_EEPROM_PAGE_LEN];
+ struct debugfs_blob_wrapper page_blobs[NSIM_MODULE_EEPROM_PAGES];
+};
+
struct nsim_ethtool {
u32 get_err;
u32 set_err;
@@ -90,6 +100,7 @@ struct nsim_ethtool {
struct ethtool_coalesce coalesce;
struct ethtool_ringparam ring;
struct ethtool_fecparam fec;
+ struct nsim_ethtool_module module;
};
struct nsim_rq {
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
` (4 preceding siblings ...)
2026-03-08 12:40 ` [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
@ 2026-03-08 12:40 ` Björn Töpel
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
6 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-08 12:40 UTC (permalink / raw)
To: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Björn Töpel, Maxime Chevallier, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
Add loopback_nsim.py with netdevsim-specific tests for CMIS module
loopback. These tests seed the EEPROM via debugfs and verify
register-level behavior.
Tests cover: no-module GET, all/partial capability reporting, EEPROM
byte verification for enable/disable and direction switching,
rejection of unsupported directions, and rejection without CMIS
support.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../selftests/drivers/net/hw/loopback_nsim.py | 249 ++++++++++++++++++
1 file changed, 249 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_nsim.py
diff --git a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
new file mode 100755
index 000000000000..ae126da671bb
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Netdevsim-specific tests for ethtool CMIS module loopback.
+
+Seeds the CMIS EEPROM via debugfs and verifies register-level
+behavior that can only be checked with controlled EEPROM contents.
+"""
+
+import errno
+import os
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import KsftSkipEx, KsftFailEx, ksft_disruptive
+from lib.py import EthtoolFamily, NlError
+from lib.py import NetDrvEnv, ip, defer
+
+# CMIS register constants matching net/ethtool/cmis_loopback.c
+SFF8024_ID_QSFP_DD = 0x18
+
+# Page 01h, Byte 142: bit 5 = Page 13h supported
+CMIS_DIAG_PAGE13_BIT = 1 << 5
+
+# Page 13h, Byte 128: loopback capability bits
+CMIS_LB_CAP_MEDIA_OUTPUT = 1 << 0
+CMIS_LB_CAP_MEDIA_INPUT = 1 << 1
+CMIS_LB_CAP_HOST_OUTPUT = 1 << 2
+CMIS_LB_CAP_HOST_INPUT = 1 << 3
+CMIS_LB_CAP_ALL = (CMIS_LB_CAP_MEDIA_OUTPUT | CMIS_LB_CAP_MEDIA_INPUT |
+ CMIS_LB_CAP_HOST_OUTPUT | CMIS_LB_CAP_HOST_INPUT)
+
+# Direction flags as YNL returns them (sets of flag name strings)
+DIR_NONE = set()
+DIR_NEAR_END = {'near-end'}
+DIR_FAR_END = {'far-end'}
+
+
+def _nsim_dfs_path(cfg):
+ """Return the per-port debugfs path for the netdevsim device."""
+ return cfg._ns.nsims[0].dfs_dir
+
+
+def _nsim_write_page_byte(cfg, page, offset, value):
+ """Write a single byte to a netdevsim EEPROM page via debugfs."""
+ if offset < 128:
+ page_file = os.path.join(_nsim_dfs_path(cfg),
+ "ethtool/module/pages/0")
+ file_offset = offset
+ else:
+ page_file = os.path.join(_nsim_dfs_path(cfg),
+ f"ethtool/module/pages/{page}")
+ file_offset = offset - 128
+
+ with open(page_file, "r+b") as f:
+ f.seek(file_offset)
+ f.write(bytes([value]))
+
+
+def _nsim_read_page_byte(cfg, page, offset):
+ """Read a single byte from a netdevsim EEPROM page via debugfs."""
+ if offset < 128:
+ page_file = os.path.join(_nsim_dfs_path(cfg),
+ "ethtool/module/pages/0")
+ file_offset = offset
+ else:
+ page_file = os.path.join(_nsim_dfs_path(cfg),
+ f"ethtool/module/pages/{page}")
+ file_offset = offset - 128
+
+ with open(page_file, "rb") as f:
+ f.seek(file_offset)
+ return f.read(1)[0]
+
+
+def _nsim_seed_cmis(cfg, caps=CMIS_LB_CAP_ALL):
+ """Seed the netdevsim EEPROM with CMIS module identity and
+ loopback capabilities.
+ """
+ _nsim_write_page_byte(cfg, 0x00, 0, SFF8024_ID_QSFP_DD)
+ _nsim_write_page_byte(cfg, 0x01, 0x8E, CMIS_DIAG_PAGE13_BIT)
+ _nsim_write_page_byte(cfg, 0x13, 0x80, caps)
+
+
+def _nsim_clear_cmis(cfg):
+ """Clear CMIS identity bytes left by previous tests."""
+ _nsim_write_page_byte(cfg, 0x00, 0, 0)
+ _nsim_write_page_byte(cfg, 0x01, 0x8E, 0)
+ _nsim_write_page_byte(cfg, 0x13, 0x80, 0)
+
+
+def _get_loopback(cfg):
+ """GET loopback and return the list of entries."""
+ result = cfg.ethnl.loopback_get({
+ 'header': {'dev-index': cfg.ifindex}
+ })
+ return result.get('entry', [])
+
+
+def _set_loopback(cfg, component, name, direction):
+ """SET loopback for a single entry."""
+ cfg.ethnl.loopback_set({
+ 'header': {'dev-index': cfg.ifindex},
+ 'entry': [{
+ 'component': component,
+ 'name': name,
+ 'direction': direction,
+ }]
+ })
+
+
+def test_get_no_module(cfg):
+ """GET on a device with no CMIS module returns no entries."""
+ _nsim_clear_cmis(cfg)
+
+ try:
+ entries = _get_loopback(cfg)
+ ksft_eq(len(entries), 0, "Expected no entries without CMIS module")
+ except NlError as e:
+ ksft_eq(e.error, errno.EOPNOTSUPP,
+ "Expected EOPNOTSUPP without CMIS module")
+
+
+def test_get_all_caps(cfg):
+ """GET with all four CMIS loopback capabilities seeded."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL)
+
+ entries = _get_loopback(cfg)
+ mod_entries = [e for e in entries if e['component'] == 'module']
+
+ # Expect 2 entries (one per name), each with both directions
+ ksft_eq(len(mod_entries), 2, "Expected 2 module loopback entries")
+
+ host = [e for e in mod_entries if e['name'] == 'cmis-host']
+ media = [e for e in mod_entries if e['name'] == 'cmis-media']
+ ksft_eq(len(host), 1, "Expected 1 cmis-host entry")
+ ksft_eq(len(media), 1, "Expected 1 cmis-media entry")
+
+ ksft_eq(host[0]['supported'], DIR_NEAR_END | DIR_FAR_END)
+ ksft_eq(media[0]['supported'], DIR_NEAR_END | DIR_FAR_END)
+
+ for e in mod_entries:
+ ksft_eq(e['direction'], DIR_NONE,
+ f"Expected direction=off for {e['name']}")
+
+
+def test_get_partial_caps(cfg):
+ """GET with only host-input capability advertised."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_HOST_INPUT)
+
+ entries = _get_loopback(cfg)
+ mod_entries = [e for e in entries if e['component'] == 'module']
+ ksft_eq(len(mod_entries), 1, "Expected 1 module loopback entry")
+ ksft_eq(mod_entries[0]['name'], 'cmis-host')
+ ksft_eq(mod_entries[0]['supported'], DIR_NEAR_END)
+
+
+@ksft_disruptive
+def test_set_verify_eeprom(cfg):
+ """SET near-end and verify the EEPROM control byte directly."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL)
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ _set_loopback(cfg, 'module', 'cmis-host', 'near-end')
+ defer(_set_loopback, cfg, 'module', 'cmis-host', 0)
+
+ # Host Side Input = Page 13h, Byte 183
+ val = _nsim_read_page_byte(cfg, 0x13, 183)
+ ksft_eq(val, 0xFF, "Host Side Input control byte should be 0xFF")
+
+ # Disable and verify
+ _set_loopback(cfg, 'module', 'cmis-host', 0)
+ val = _nsim_read_page_byte(cfg, 0x13, 183)
+ ksft_eq(val, 0x00, "Host Side Input should be 0x00 after disable")
+
+
+@ksft_disruptive
+def test_set_direction_switch_eeprom(cfg):
+ """Switch directions and verify both EEPROM bytes."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL)
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ _set_loopback(cfg, 'module', 'cmis-host', 'near-end')
+ defer(_set_loopback, cfg, 'module', 'cmis-host', 0)
+
+ # Switch to far-end
+ _set_loopback(cfg, 'module', 'cmis-host', 'far-end')
+
+ # Near-end (Host Input, Byte 183) should be disabled
+ val = _nsim_read_page_byte(cfg, 0x13, 183)
+ ksft_eq(val, 0x00, "Near-end should be disabled after switch")
+ # Far-end (Host Output, Byte 182) should be enabled
+ val = _nsim_read_page_byte(cfg, 0x13, 182)
+ ksft_eq(val, 0xFF, "Far-end should be enabled after switch")
+
+
+@ksft_disruptive
+def test_set_unsupported_direction(cfg):
+ """SET with unsupported direction should fail."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_HOST_INPUT) # only near-end
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ try:
+ _set_loopback(cfg, 'module', 'cmis-host', 'far-end')
+ raise KsftFailEx("Should have rejected unsupported direction")
+ except NlError as e:
+ ksft_eq(e.error, errno.EOPNOTSUPP,
+ "Expected EOPNOTSUPP for unsupported direction")
+
+
+@ksft_disruptive
+def test_set_no_cmis(cfg):
+ """SET on a device without CMIS loopback support should fail."""
+ _nsim_clear_cmis(cfg)
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ try:
+ _set_loopback(cfg, 'module', 'cmis-host', 'near-end')
+ raise KsftFailEx("Should have rejected SET without CMIS support")
+ except NlError as e:
+ ksft_eq(e.error, errno.EOPNOTSUPP,
+ "Expected EOPNOTSUPP without CMIS support")
+
+
+def main() -> None:
+ with NetDrvEnv(__file__) as cfg:
+ cfg.ethnl = EthtoolFamily()
+
+ ksft_run([
+ test_get_no_module,
+ test_get_all_caps,
+ test_get_partial_caps,
+ test_set_verify_eeprom,
+ test_set_direction_switch_eeprom,
+ test_set_unsupported_direction,
+ test_set_no_cmis,
+ ], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
@ 2026-03-09 7:34 ` Maxime Chevallier
2026-03-09 8:21 ` Björn Töpel
0 siblings, 1 reply; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-09 7:34 UTC (permalink / raw)
To: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Hi Björn,
On 08/03/2026 13:40, Björn Töpel wrote:
> Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET,
> ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using
> the standard ethnl_request_ops infrastructure.
>
> GET collects loopback entries from per-component helpers via
> loopback_get_entries(). SET parses the nested entry attributes,
> dispatches each to loopback_set_one(), and only sends a notification
> when the state is changed.
>
> No components are wired yet.
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
At a first glance, what I see is that you're using the GET ->doit method
to retrieve an array of loopback entries. The pefered approach in that
case is to use the GET ->dumpit command instead, issueing a netlink DUMP
request to list all available loopback entities on a given netdev.
If you want some reference on that, take a look at the phy.c + the
'perphy' helpers in net/ethtool/netlink.c
The idea is that you can pass a netdev ifindex in the header of the DUMP
request, which you can use to dump all loopbacks the passed netdev.
You can also check the ethtool code itself, you'll see that when you use
the "ethtool --show-phys eth0" command for example, it issues a DUMP
request to the kernel.
I'll continue the review w.r.t the actual content of the messages :)
Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation
2026-03-09 7:34 ` Maxime Chevallier
@ 2026-03-09 8:21 ` Björn Töpel
2026-03-09 14:51 ` Björn Töpel
0 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-09 8:21 UTC (permalink / raw)
To: Maxime Chevallier, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Maxime Chevallier <maxime.chevallier@bootlin.com> writes:
> Hi Björn,
>
> On 08/03/2026 13:40, Björn Töpel wrote:
>> Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET,
>> ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using
>> the standard ethnl_request_ops infrastructure.
>>
>> GET collects loopback entries from per-component helpers via
>> loopback_get_entries(). SET parses the nested entry attributes,
>> dispatches each to loopback_set_one(), and only sends a notification
>> when the state is changed.
>>
>> No components are wired yet.
>>
>> Signed-off-by: Björn Töpel <bjorn@kernel.org>
>
> At a first glance, what I see is that you're using the GET ->doit method
> to retrieve an array of loopback entries. The pefered approach in that
> case is to use the GET ->dumpit command instead, issueing a netlink DUMP
> request to list all available loopback entities on a given netdev.
>
> If you want some reference on that, take a look at the phy.c + the
> 'perphy' helpers in net/ethtool/netlink.c
>
> The idea is that you can pass a netdev ifindex in the header of the DUMP
> request, which you can use to dump all loopbacks the passed netdev.
>
> You can also check the ethtool code itself, you'll see that when you use
> the "ethtool --show-phys eth0" command for example, it issues a DUMP
> request to the kernel.
Ah, got it! Thanks!
> I'll continue the review w.r.t the actual content of the messages :)
...and thank you!
Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
` (5 preceding siblings ...)
2026-03-08 12:40 ` [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
@ 2026-03-09 13:49 ` Naveen Mamindlapalli
2026-03-09 14:55 ` Björn Töpel
6 siblings, 1 reply; 23+ messages in thread
From: Naveen Mamindlapalli @ 2026-03-09 13:49 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn, Maxime Chevallier,
Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
On 2026-03-08 at 18:10:06, Björn Töpel (bjorn@kernel.org) wrote:
> Hi!
>
> Background
> ==========
>
> This is the v2 RFC of the CMIS loopback series, reworked based on
> feedback from v1 [1].
>
> The main change is that loopback is no longer bolted onto the existing
> module interface. Instead, it gets its own netlink commands
> (LOOPBACK_GET/SET) with a generic component/name/direction model that
> can represent loopback points across the entire data path -- MODULE,
> PHY, MAC, and PCS. This series wires up MODULE/CMIS as the first user;
> the other component types return -EOPNOTSUPP for now.
>
> The Common Management Interface Specification (CMIS) defines four
> diagnostic loopback types, characterized by location (Host or Media
> Side) and signal direction:
>
> - Host Side Input (Rx->Tx) -- near-end
> - Host Side Output (Tx->Rx) -- far-end
> - Media Side Input (Rx->Tx) -- near-end
> - Media Side Output (Tx->Rx) -- far-end
>
> Support is detected via Page 13h Byte 128, and loopback is controlled
> via Page 13h Bytes 180-183 (one byte per type, one bit per lane).
>
> The CMIS helpers work entirely over get/set_module_eeprom_by_page, so
> any driver that already has EEPROM page access gets module loopback
> without new ethtool_ops or driver changes.
>
> Implementation
> ==============
>
> Patch 1/6 ethtool: Add loopback netlink UAPI definitions
> Adds the YAML spec and generated UAPI header for the new
> LOOPBACK_GET/SET commands. Each loopback entry carries a component
> type, optional id, name string, supported directions bitmask, and
> current direction.
>
> Patch 2/6 ethtool: Add loopback GET/SET netlink implementation
> Implements GET/SET dispatch in a new loopback.c. GET collects
> entries from each subsystem. SET switches on the component and
> calls the right handler per entry. No components are wired yet.
>
> Patch 3/6 ethtool: add CMIS loopback helpers for module loopback control
> Adds cmis_loopback.c with the MODULE component handlers. GET reads
> Page 13h and appends one entry per supported loopback point
> ("cmis-host" and/or "cmis-media"). SET resolves name to control
> byte indices and enforces mutual exclusivity -- switching directions
> first disables the active one in a separate EEPROM write, then
> enables the new one.
>
> Patch 4/6 selftests: drv-net: Add loopback driver test
> Adds loopback_drv.py with generic tests that work on any device
> with module loopback support: enable/disable, direction switching,
> idempotent enable, and rejection while interface is up.
>
> Patch 5/6 netdevsim: Add module EEPROM simulation via debugfs
> Adds get/set_module_eeprom_by_page to netdevsim, backed by a
> 256-page x 128-byte array exposed via debugfs. This enables
> testing the CMIS loopback path without hardware.
>
> Patch 6/6 selftests: drv-net: Add CMIS loopback netdevsim test
> Adds loopback_nsim.py with netdevsim-specific tests that seed the
> EEPROM via debugfs: capability reporting, EEPROM byte verification,
> and error paths for unsupported or missing CMIS support.
>
> Limitations
> ===========
>
> Only MODULE/CMIS is wired up. PHY, MAC, and PCS loopback are defined
> in the UAPI but not yet implemented.
>
> No per-lane support -- loopback is all-or-nothing (0xFF/0x00) across
> lanes.
>
> Extending to PHY/MAC/PCS
> =========================
>
> PHY loopback can walk phy_link_topology to enumerate PHYs by phyindex
> and call phy_loopback() directly. MAC and PCS loopback can route
> through phylink via new mac_set_loopback()/pcs_set_loopback()
> callbacks. Drivers that don't use phylink could add new ethtool_ops.
> The dispatch framework already handles all component types.
>
> Open questions
> ==============
>
> - Is this the right extensibility model? I'd appreciate input from
> other NIC vendors on whether component/name/direction is flexible
> enough for their loopback implementations. Also, from the PHY/port
> folks (Maxime, Russell)!
Hi Bjorn,
The component/name/direction model in v2 fits our hardware well.
I am working on loopback support for Marvell OcteonTX2.
The MAC (RPM block) supports a PCS-level loopback. In addition,
the on-chip SerDes (GSERM) is managed by embedded firmware and
supports three more loopback modes:
NED (Near-End Digital) -- digital domain, before the analog front-end
NEA (Near-End Analog) -- through the full analog front-end
FED (Far-End Digital) -- line-side traffic looped back
Since the GSERM is not a phylib phy_device, both the MAC PCS
loopback and the SerDes loopbacks fall under the MAC component
in your model.
Mapped to the v2 model:
component name supported description
MAC mac near-end PCS-level loopback
MAC serdes-ned near-end digital only
MAC serdes-nea near-end analog
MAC serdes-fed far-end line-side
The SerDes NED and NEA both have the same (component, direction).
Both are (MAC, near-end) -- but exercise fundamentally different
hardware paths. The name field distinguishes them as per your model,
I can work on MAC + SerDes loopback driver support for CN10K and
post patches on top of your series once MAC component dispatch is
in place.
Thanks,
Naveen
>
> - The MODULE id field is currently unused. For multi-module setups it
> could serve as a port selector. It could also help detect module
> swaps -- a hash of the CMIS vendor serial number (Page 00h, Bytes
> 168-183), vendor name, and part number would give userspace a
> stable identifier to verify the module hasn't changed since
> loopback was configured. Worth adding now, or defer until there's a
> concrete user?
>
> - Are patches 5-6 (netdevsim EEPROM simulation + netdevsim-specific
> tests) worth carrying? They drive the CMIS Page 13h registers from
> debugfs, which gives good coverage without hardware, but it's
> another netdevsim surface to maintain. If the consensus is that the
> generic driver tests (patch 4) are sufficient, I'm happy to drop
> them.
>
> Related work
> ============
>
> [1] CMIS loopback v1
> https://lore.kernel.org/netdev/20260219130050.2390226-1-bjorn@kernel.org/
> [2] New loopback modes
> https://lore.kernel.org/netdev/20251024044849.1098222-1-hkelam@marvell.com/
> [3] PHY loopback
> https://lore.kernel.org/netdev/20240911212713.2178943-1-maxime.chevallier@bootlin.com/
> [4] bnxt_en: add .set_module_eeprom_by_page() support
> https://lore.kernel.org/netdev/20250310183129.3154117-8-michael.chan@broadcom.com/
>
>
> Björn Töpel (6):
> ethtool: Add loopback netlink UAPI definitions
> ethtool: Add loopback GET/SET netlink implementation
> ethtool: add CMIS loopback helpers for module loopback control
> selftests: drv-net: Add loopback driver test
> netdevsim: Add module EEPROM simulation via debugfs
> selftests: drv-net: Add CMIS loopback netdevsim test
>
> Documentation/netlink/specs/ethtool.yaml | 115 ++++++
> drivers/net/netdevsim/ethtool.c | 79 ++++
> drivers/net/netdevsim/netdevsim.h | 11 +
> include/linux/ethtool.h | 28 ++
> .../uapi/linux/ethtool_netlink_generated.h | 52 +++
> net/ethtool/Makefile | 2 +-
> net/ethtool/cmis_loopback.c | 338 ++++++++++++++++++
> net/ethtool/loopback.c | 248 +++++++++++++
> net/ethtool/netlink.c | 20 ++
> net/ethtool/netlink.h | 8 +
> .../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++++
> .../selftests/drivers/net/hw/loopback_nsim.py | 249 +++++++++++++
> 12 files changed, 1376 insertions(+), 1 deletion(-)
> create mode 100644 net/ethtool/cmis_loopback.c
> create mode 100644 net/ethtool/loopback.c
> create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py
> create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_nsim.py
>
>
> base-commit: 0bcac7b11262557c990da1ac564d45777eb6b005
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
@ 2026-03-09 14:16 ` Maxime Chevallier
2026-03-09 14:59 ` Björn Töpel
2026-03-09 16:45 ` Andrew Lunn
0 siblings, 2 replies; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-09 14:16 UTC (permalink / raw)
To: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Hi,
On 08/03/2026 13:40, Björn Töpel wrote:
> Add the netlink YAML spec and auto-generated UAPI header for a unified
> loopback interface covering MAC, PCS, PHY, and pluggable module
> components.
>
> Each loopback point is described by a nested entry attribute
> containing:
>
> - component where in the path (MAC, PCS, PHY, MODULE)
> - name subsystem label, e.g. "cmis-host" or "cmis-media"
> - id optional instance selector (e.g. PHY id, port id)
> - supported bitmask of supported directions
> - direction NEAR_END, FAR_END, or 0 (disabled)
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
> ---
> Documentation/netlink/specs/ethtool.yaml | 115 ++++++++++++++++++
> .../uapi/linux/ethtool_netlink_generated.h | 52 ++++++++
> 2 files changed, 167 insertions(+)
>
> diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
> index 4707063af3b4..05ebad6ae4e0 100644
> --- a/Documentation/netlink/specs/ethtool.yaml
> +++ b/Documentation/netlink/specs/ethtool.yaml
> @@ -211,6 +211,39 @@ definitions:
> name: discard
> value: 31
>
> + -
> + name: loopback-component
> + type: enum
> + doc: |
> + Loopback component. Identifies where in the network path the
> + loopback is applied.
> + entries:
> + -
> + name: mac
> + doc: MAC loopback
> + -
> + name: pcs
> + doc: PCS loopback
> + -
> + name: phy
> + doc: PHY loopback
> + -
> + name: module
> + doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
Should we also add "serdes" ?
> + -
> + name: loopback-direction
> + type: flags
> + doc: |
> + Loopback direction flags. Used as a bitmask in supported, and as
> + a single value in direction.
> + entries:
> + -
> + name: near-end
> + doc: Near-end loopback; host-loop-host
> + -
> + name: far-end
> + doc: Far-end loopback; line-loop-line
> +
> attribute-sets:
> -
> name: header
> @@ -1903,6 +1936,60 @@ attribute-sets:
> name: link
> type: nest
> nested-attributes: mse-snapshot
> + -
> + name: loopback-entry
> + doc: Per-component loopback configuration entry.
> + attr-cnt-name: __ethtool-a-loopback-entry-cnt
> + attributes:
> + -
> + name: unspec
> + type: unused
> + value: 0
> + -
> + name: component
> + type: u32
> + enum: loopback-component
> + doc: Loopback component
> + -
> + name: id
> + type: u32
> + doc: |
> + Optional component instance identifier. Required for PHY,
> + optional for MODULE, omitted for MAC and PCS.
it doesn't have to be required for PHY. The current idea is that if you
don't pass any PHY index when issueing a PHY-targetting command, then it
means you're targetting net_device->phydev, that is the PHY device
attached to the netdev (if any).
I think we can keep that behaviour, as systems with multiple PHYs are
not very common.
> + -
> + name: name
> + type: string
> + doc: |
> + Subsystem-specific name for the loopback point within the
> + component.
We'll need to be careful about keeping this subsystem-specific and not
driver-specific :)
> + -
> + name: supported
> + type: u32
> + enum: loopback-direction
> + enum-as-flags: true
> + doc: Bitmask of supported loopback directions
> + -
> + name: direction
> + type: u32
> + enum: loopback-direction
> + doc: Current loopback direction, 0 means disabled
no need for an u32 for 3 different values I think :)
Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation
2026-03-09 8:21 ` Björn Töpel
@ 2026-03-09 14:51 ` Björn Töpel
2026-03-09 16:14 ` Maxime Chevallier
0 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-09 14:51 UTC (permalink / raw)
To: Maxime Chevallier, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Björn Töpel <bjorn@kernel.org> writes:
> Maxime Chevallier <maxime.chevallier@bootlin.com> writes:
>
>> Hi Björn,
>>
>> On 08/03/2026 13:40, Björn Töpel wrote:
>>> Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET,
>>> ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using
>>> the standard ethnl_request_ops infrastructure.
>>>
>>> GET collects loopback entries from per-component helpers via
>>> loopback_get_entries(). SET parses the nested entry attributes,
>>> dispatches each to loopback_set_one(), and only sends a notification
>>> when the state is changed.
>>>
>>> No components are wired yet.
>>>
>>> Signed-off-by: Björn Töpel <bjorn@kernel.org>
>>
>> At a first glance, what I see is that you're using the GET ->doit method
>> to retrieve an array of loopback entries. The pefered approach in that
>> case is to use the GET ->dumpit command instead, issueing a netlink DUMP
>> request to list all available loopback entities on a given netdev.
>>
>> If you want some reference on that, take a look at the phy.c + the
>> 'perphy' helpers in net/ethtool/netlink.c
>>
>> The idea is that you can pass a netdev ifindex in the header of the DUMP
>> request, which you can use to dump all loopbacks the passed netdev.
>>
>> You can also check the ethtool code itself, you'll see that when you use
>> the "ethtool --show-phys eth0" command for example, it issues a DUMP
>> request to the kernel.
>
> Ah, got it! Thanks!
Thanks for pointing this out! I like how using this can get rid of the
fixed size array in struct ethtool_loopback_cfg, which had a bad smell
to me.
Thinking out loud -- Now, using a similar scheme for loopback requires
some thought around the sub-iterator: Loopback has "multiple"
sub-iterator: (component, [id], name)... or needs scheme to deal with
one sub-iter, and loopback can share a lot of code with the
perphy-functions.
Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
@ 2026-03-09 14:55 ` Björn Töpel
2026-03-10 7:35 ` Naveen Mamindlapalli
0 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-09 14:55 UTC (permalink / raw)
To: Naveen Mamindlapalli
Cc: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn, Maxime Chevallier,
Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Naveen!
Naveen Mamindlapalli <naveenm@marvell.com> writes:
>> Open questions
>> ==============
>>
>> - Is this the right extensibility model? I'd appreciate input from
>> other NIC vendors on whether component/name/direction is flexible
>> enough for their loopback implementations. Also, from the PHY/port
>> folks (Maxime, Russell)!
>
> Hi Bjorn,
>
> The component/name/direction model in v2 fits our hardware well.
>
> I am working on loopback support for Marvell OcteonTX2.
> The MAC (RPM block) supports a PCS-level loopback. In addition,
> the on-chip SerDes (GSERM) is managed by embedded firmware and
> supports three more loopback modes:
> NED (Near-End Digital) -- digital domain, before the analog front-end
> NEA (Near-End Analog) -- through the full analog front-end
> FED (Far-End Digital) -- line-side traffic looped back
>
> Since the GSERM is not a phylib phy_device, both the MAC PCS
> loopback and the SerDes loopbacks fall under the MAC component
> in your model.
>
> Mapped to the v2 model:
> component name supported description
> MAC mac near-end PCS-level loopback
> MAC serdes-ned near-end digital only
> MAC serdes-nea near-end analog
> MAC serdes-fed far-end line-side
>
> The SerDes NED and NEA both have the same (component, direction).
> Both are (MAC, near-end) -- but exercise fundamentally different
> hardware paths. The name field distinguishes them as per your model,
Ok! ...and MAC+serdes makes sense from your PoV? Or do we need a new
component "SERDES" (as Maxime points out in another reply)?
> I can work on MAC + SerDes loopback driver support for CN10K and
> post patches on top of your series once MAC component dispatch is
> in place.
Got it! Thanks!
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-09 14:16 ` Maxime Chevallier
@ 2026-03-09 14:59 ` Björn Töpel
2026-03-09 16:45 ` Andrew Lunn
1 sibling, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-09 14:59 UTC (permalink / raw)
To: Maxime Chevallier, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
Maxime Chevallier <maxime.chevallier@bootlin.com> writes:
>> diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
>> index 4707063af3b4..05ebad6ae4e0 100644
>> --- a/Documentation/netlink/specs/ethtool.yaml
>> +++ b/Documentation/netlink/specs/ethtool.yaml
>> @@ -211,6 +211,39 @@ definitions:
>> name: discard
>> value: 31
>>
>> + -
>> + name: loopback-component
>> + type: enum
>> + doc: |
>> + Loopback component. Identifies where in the network path the
>> + loopback is applied.
>> + entries:
>> + -
>> + name: mac
>> + doc: MAC loopback
>> + -
>> + name: pcs
>> + doc: PCS loopback
>> + -
>> + name: phy
>> + doc: PHY loopback
>> + -
>> + name: module
>> + doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
>
> Should we also add "serdes" ?
Good question! I've asked Naveen in another reply...
>> @@ -1903,6 +1936,60 @@ attribute-sets:
>> name: link
>> type: nest
>> nested-attributes: mse-snapshot
>> + -
>> + name: loopback-entry
>> + doc: Per-component loopback configuration entry.
>> + attr-cnt-name: __ethtool-a-loopback-entry-cnt
>> + attributes:
>> + -
>> + name: unspec
>> + type: unused
>> + value: 0
>> + -
>> + name: component
>> + type: u32
>> + enum: loopback-component
>> + doc: Loopback component
>> + -
>> + name: id
>> + type: u32
>> + doc: |
>> + Optional component instance identifier. Required for PHY,
>> + optional for MODULE, omitted for MAC and PCS.
>
> it doesn't have to be required for PHY. The current idea is that if you
> don't pass any PHY index when issueing a PHY-targetting command, then it
> means you're targetting net_device->phydev, that is the PHY device
> attached to the netdev (if any).
>
> I think we can keep that behaviour, as systems with multiple PHYs are
> not very common.
Got it! I'll update the wording! Thanks for the clarification!
>> + -
>> + name: name
>> + type: string
>> + doc: |
>> + Subsystem-specific name for the loopback point within the
>> + component.
>
> We'll need to be careful about keeping this subsystem-specific and not
> driver-specific :)
>
>> + -
>> + name: supported
>> + type: u32
>> + enum: loopback-direction
>> + enum-as-flags: true
>> + doc: Bitmask of supported loopback directions
>> + -
>> + name: direction
>> + type: u32
>> + enum: loopback-direction
>> + doc: Current loopback direction, 0 means disabled
>
> no need for an u32 for 3 different values I think :)
ACK! ...or maybe uint (which will use as least 32, but shows that we
don't care)?
Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation
2026-03-09 14:51 ` Björn Töpel
@ 2026-03-09 16:14 ` Maxime Chevallier
0 siblings, 0 replies; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-09 16:14 UTC (permalink / raw)
To: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn
Cc: Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
On 09/03/2026 15:51, Björn Töpel wrote:
> Björn Töpel <bjorn@kernel.org> writes:
>
>> Maxime Chevallier <maxime.chevallier@bootlin.com> writes:
>>
>>> Hi Björn,
>>>
>>> On 08/03/2026 13:40, Björn Töpel wrote:
>>>> Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET,
>>>> ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using
>>>> the standard ethnl_request_ops infrastructure.
>>>>
>>>> GET collects loopback entries from per-component helpers via
>>>> loopback_get_entries(). SET parses the nested entry attributes,
>>>> dispatches each to loopback_set_one(), and only sends a notification
>>>> when the state is changed.
>>>>
>>>> No components are wired yet.
>>>>
>>>> Signed-off-by: Björn Töpel <bjorn@kernel.org>
>>>
>>> At a first glance, what I see is that you're using the GET ->doit method
>>> to retrieve an array of loopback entries. The pefered approach in that
>>> case is to use the GET ->dumpit command instead, issueing a netlink DUMP
>>> request to list all available loopback entities on a given netdev.
>>>
>>> If you want some reference on that, take a look at the phy.c + the
>>> 'perphy' helpers in net/ethtool/netlink.c
>>>
>>> The idea is that you can pass a netdev ifindex in the header of the DUMP
>>> request, which you can use to dump all loopbacks the passed netdev.
>>>
>>> You can also check the ethtool code itself, you'll see that when you use
>>> the "ethtool --show-phys eth0" command for example, it issues a DUMP
>>> request to the kernel.
>>
>> Ah, got it! Thanks!
>
> Thanks for pointing this out! I like how using this can get rid of the
> fixed size array in struct ethtool_loopback_cfg, which had a bad smell
> to me.
>
> Thinking out loud -- Now, using a similar scheme for loopback requires
> some thought around the sub-iterator: Loopback has "multiple"
> sub-iterator: (component, [id], name)... or needs scheme to deal with
> one sub-iter, and loopback can share a lot of code with the
> perphy-functions.
yeah, at some point in the past I played around with the idea of more
generic ethnl helpers for filtered DUMP requests :
https://lore.kernel.org/netdev/20250324104012.367366-1-maxime.chevallier@bootlin.com/
At that time, the PHY usecase was the main user so I ended-up ditching
that in favor of the more specific perphy helpers we have today, after
discussing the matter with Jakub.
Now that we're seeing more and more ethnl DUMP commands that can take a
netdev ifindex in the request, maybe some of that code above can be useful
for you and we can resurect this approach ?
Maxime
>
>
> Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-09 14:16 ` Maxime Chevallier
2026-03-09 14:59 ` Björn Töpel
@ 2026-03-09 16:45 ` Andrew Lunn
2026-03-10 10:23 ` Maxime Chevallier
1 sibling, 1 reply; 23+ messages in thread
From: Andrew Lunn @ 2026-03-09 16:45 UTC (permalink / raw)
To: Maxime Chevallier
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
> > + doc: |
> > + Loopback component. Identifies where in the network path the
> > + loopback is applied.
> > + entries:
> > + -
> > + name: mac
> > + doc: MAC loopback
> > + -
> > + name: pcs
> > + doc: PCS loopback
> > + -
> > + name: phy
> > + doc: PHY loopback
> > + -
> > + name: module
> > + doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
>
> Should we also add "serdes" ?
What is the difference between SERDES and PCS?
Maybe we should also make it clear PHY means Ethernet PHY. The Marvell
Generic PHYs have
phy-mvebu-a3700-comphy.c:#define COMPHY_DIG_LOOPBACK_EN 0x23
phy-mvebu-cp110-comphy.c:#define MVEBU_COMPHY_LOOPBACK(n) (0x88c + (n) * 0x1000)
which suggests it can also do loopback. We don't want PHY meaning two
different things.
And maybe we should comment that a Base-T SFP module which contains an
Ethernet PHY should appear under PHY, not module? When Linux is
driving the PHY, that should just happen. But if it is firmware
driving the PHY in the module, we should make our expectations of the
firmware clear.
Andrew
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-09 14:55 ` Björn Töpel
@ 2026-03-10 7:35 ` Naveen Mamindlapalli
2026-03-10 14:00 ` Andrew Lunn
0 siblings, 1 reply; 23+ messages in thread
From: Naveen Mamindlapalli @ 2026-03-10 7:35 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, Donald Hunter, Jakub Kicinski, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Saeed Mahameed,
Tariq Toukan, Leon Romanovsky, Andrew Lunn, Maxime Chevallier,
Andrew Lunn, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
On 2026-03-09 at 20:25:51, Björn Töpel (bjorn@kernel.org) wrote:
> Naveen!
>
> Naveen Mamindlapalli <naveenm@marvell.com> writes:
>
> >> Open questions
> >> ==============
> >>
> >> - Is this the right extensibility model? I'd appreciate input from
> >> other NIC vendors on whether component/name/direction is flexible
> >> enough for their loopback implementations. Also, from the PHY/port
> >> folks (Maxime, Russell)!
> >
> > Hi Bjorn,
> >
> > The component/name/direction model in v2 fits our hardware well.
> >
> > I am working on loopback support for Marvell OcteonTX2.
> > The MAC (RPM block) supports a PCS-level loopback. In addition,
> > the on-chip SerDes (GSERM) is managed by embedded firmware and
> > supports three more loopback modes:
> > NED (Near-End Digital) -- digital domain, before the analog front-end
> > NEA (Near-End Analog) -- through the full analog front-end
> > FED (Far-End Digital) -- line-side traffic looped back
> >
> > Since the GSERM is not a phylib phy_device, both the MAC PCS
> > loopback and the SerDes loopbacks fall under the MAC component
> > in your model.
> >
> > Mapped to the v2 model:
> > component name supported description
> > MAC mac near-end PCS-level loopback
> > MAC serdes-ned near-end digital only
> > MAC serdes-nea near-end analog
> > MAC serdes-fed far-end line-side
> >
> > The SerDes NED and NEA both have the same (component, direction).
> > Both are (MAC, near-end) -- but exercise fundamentally different
> > hardware paths. The name field distinguishes them as per your model,
>
> Ok! ...and MAC+serdes makes sense from your PoV? Or do we need a new
> component "SERDES" (as Maxime points out in another reply)?
>
In my earlier comment I mapped the SerDes loopbacks under the MAC
component to fit the current model, but a separate SERDES component
as Maxime suggests would be a better fit for our hardware.
On OcteonTX2 SoC, MAC (PCS) and SerDes are separate hardware blocks.
Each block has its own loopback controls.
With a SERDES component, the mapping becomes cleaner:
component name supported
MAC mac near-end
SERDES serdes-ned near-end
SERDES serdes-nea near-end
SERDES serdes-fed far-end
Thanks,
Naveen
> > I can work on MAC + SerDes loopback driver support for CN10K and
> > post patches on top of your series once MAC component dispatch is
> > in place.
>
> Got it! Thanks!
>
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-09 16:45 ` Andrew Lunn
@ 2026-03-10 10:23 ` Maxime Chevallier
2026-03-10 13:56 ` Andrew Lunn
0 siblings, 1 reply; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-10 10:23 UTC (permalink / raw)
To: Andrew Lunn
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
On 09/03/2026 17:45, Andrew Lunn wrote:
>>> + doc: |
>>> + Loopback component. Identifies where in the network path the
>>> + loopback is applied.
>>> + entries:
>>> + -
>>> + name: mac
>>> + doc: MAC loopback
>>> + -
>>> + name: pcs
>>> + doc: PCS loopback
>>> + -
>>> + name: phy
>>> + doc: PHY loopback
>>> + -
>>> + name: module
>>> + doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
>>
>> Should we also add "serdes" ?
>
> What is the difference between SERDES and PCS?
By Serdes I mean "generic PHY", but as you state below I don't really
want to use the word "PHY" as it's very prone to confusion with Ethernet
PHYs.
>
> Maybe we should also make it clear PHY means Ethernet PHY. The Marvell
> Generic PHYs have
>
> phy-mvebu-a3700-comphy.c:#define COMPHY_DIG_LOOPBACK_EN 0x23
> phy-mvebu-cp110-comphy.c:#define MVEBU_COMPHY_LOOPBACK(n) (0x88c + (n) * 0x1000)
>
> which suggests it can also do loopback. We don't want PHY meaning two
> different things.
Agreed,
Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions
2026-03-10 10:23 ` Maxime Chevallier
@ 2026-03-10 13:56 ` Andrew Lunn
0 siblings, 0 replies; 23+ messages in thread
From: Andrew Lunn @ 2026-03-10 13:56 UTC (permalink / raw)
To: Maxime Chevallier
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Michael Chan, Hariprasad Kelam, Ido Schimmel, Danielle Ratson,
linux-kernel, linux-rdma, Russell King
On Tue, Mar 10, 2026 at 11:23:48AM +0100, Maxime Chevallier wrote:
>
>
> On 09/03/2026 17:45, Andrew Lunn wrote:
> >>> + doc: |
> >>> + Loopback component. Identifies where in the network path the
> >>> + loopback is applied.
> >>> + entries:
> >>> + -
> >>> + name: mac
> >>> + doc: MAC loopback
> >>> + -
> >>> + name: pcs
> >>> + doc: PCS loopback
> >>> + -
> >>> + name: phy
> >>> + doc: PHY loopback
> >>> + -
> >>> + name: module
> >>> + doc: Pluggable module (e.g. CMIS (Q)SFP) loopback
> >>
> >> Should we also add "serdes" ?
> >
> > What is the difference between SERDES and PCS?
>
> By Serdes I mean "generic PHY", but as you state below I don't really
> want to use the word "PHY" as it's very prone to confusion with Ethernet
> PHYs.
We probably want more than a minimum for doc: We actually want a full
sentence, maybe a paragraph, clearly defining what we mean by each
entry.
We also need to the careful with generic PHY and serdes. Marvell's
comment was that they have multiple loopback points, and named some of
those with -serdes. Is that actually a PCS? Or is it the same
functionality as a generic PHY, just not implemented as a Linux
generic PHY?
We have to assume vendors will get names wrong, because vendors often
get names wrong. Ideally we want to point to clauses in 802.3, since
it is very hard to argue against that.
Andrew
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-10 7:35 ` Naveen Mamindlapalli
@ 2026-03-10 14:00 ` Andrew Lunn
2026-03-11 5:59 ` Naveen Mamindlapalli
0 siblings, 1 reply; 23+ messages in thread
From: Andrew Lunn @ 2026-03-10 14:00 UTC (permalink / raw)
To: Naveen Mamindlapalli
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Maxime Chevallier, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
> > > Since the GSERM is not a phylib phy_device, both the MAC PCS
> > > loopback and the SerDes loopbacks fall under the MAC component
> > > in your model.
> > >
> > > Mapped to the v2 model:
> > > component name supported description
> > > MAC mac near-end PCS-level loopback
> > > MAC serdes-ned near-end digital only
> > > MAC serdes-nea near-end analog
> > > MAC serdes-fed far-end line-side
> > >
> > > The SerDes NED and NEA both have the same (component, direction).
> > > Both are (MAC, near-end) -- but exercise fundamentally different
> > > hardware paths. The name field distinguishes them as per your model,
> >
> > Ok! ...and MAC+serdes makes sense from your PoV? Or do we need a new
> > component "SERDES" (as Maxime points out in another reply)?
> >
>
> In my earlier comment I mapped the SerDes loopbacks under the MAC
> component to fit the current model, but a separate SERDES component
> as Maxime suggests would be a better fit for our hardware.
>
> On OcteonTX2 SoC, MAC (PCS) and SerDes are separate hardware blocks.
> Each block has its own loopback controls.
>
> With a SERDES component, the mapping becomes cleaner:
> component name supported
> MAC mac near-end
> SERDES serdes-ned near-end
> SERDES serdes-nea near-end
> SERDES serdes-fed far-end
If Linux where to drive the SERDES, what part of Linux would it be?
Generic PHY? How does your SERDES hardware block fit into 802.3? Which
clause describes it?
Thanks
Andrew
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-10 14:00 ` Andrew Lunn
@ 2026-03-11 5:59 ` Naveen Mamindlapalli
2026-03-11 12:32 ` Andrew Lunn
0 siblings, 1 reply; 23+ messages in thread
From: Naveen Mamindlapalli @ 2026-03-11 5:59 UTC (permalink / raw)
To: Andrew Lunn
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Maxime Chevallier, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
On 2026-03-10 at 19:30:14, Andrew Lunn (andrew@lunn.ch) wrote:
> > > > Since the GSERM is not a phylib phy_device, both the MAC PCS
> > > > loopback and the SerDes loopbacks fall under the MAC component
> > > > in your model.
> > > >
> > > > Mapped to the v2 model:
> > > > component name supported description
> > > > MAC mac near-end PCS-level loopback
> > > > MAC serdes-ned near-end digital only
> > > > MAC serdes-nea near-end analog
> > > > MAC serdes-fed far-end line-side
> > > >
> > > > The SerDes NED and NEA both have the same (component, direction).
> > > > Both are (MAC, near-end) -- but exercise fundamentally different
> > > > hardware paths. The name field distinguishes them as per your model,
> > >
> > > Ok! ...and MAC+serdes makes sense from your PoV? Or do we need a new
> > > component "SERDES" (as Maxime points out in another reply)?
> > >
> >
> > In my earlier comment I mapped the SerDes loopbacks under the MAC
> > component to fit the current model, but a separate SERDES component
> > as Maxime suggests would be a better fit for our hardware.
> >
> > On OcteonTX2 SoC, MAC (PCS) and SerDes are separate hardware blocks.
> > Each block has its own loopback controls.
> >
> > With a SERDES component, the mapping becomes cleaner:
> > component name supported
> > MAC mac near-end
> > SERDES serdes-ned near-end
> > SERDES serdes-nea near-end
> > SERDES serdes-fed far-end
>
> If Linux where to drive the SERDES, what part of Linux would it be?
> Generic PHY? How does your SERDES hardware block fit into 802.3? Which
> clause describes it?
Hi Andrew,
On OcteonTx2 SoC, the SerDes (GSERM) is a HW block integrated into the
SoC die. It is not on an MDIO bus or any bus that Linux can enumerate.
The block is fully managed by the firmware running on the SoC. The NIC
driver configures it indirectly through firmware mailbox commands.
The data path looks like:
MAC (RPM) --- SerDes (GSERM) --- module/PHY
In 802.3 terms, the closest match would be PMA. The GSERM handles
serialization/deserialization and the analog front-end.
Thanks,
Naveen
>
> Thanks
> Andrew
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-11 5:59 ` Naveen Mamindlapalli
@ 2026-03-11 12:32 ` Andrew Lunn
2026-03-11 16:52 ` Russell King (Oracle)
0 siblings, 1 reply; 23+ messages in thread
From: Andrew Lunn @ 2026-03-11 12:32 UTC (permalink / raw)
To: Naveen Mamindlapalli
Cc: Björn Töpel, netdev, Donald Hunter, Jakub Kicinski,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Saeed Mahameed, Tariq Toukan, Leon Romanovsky, Andrew Lunn,
Maxime Chevallier, Michael Chan, Hariprasad Kelam, Ido Schimmel,
Danielle Ratson, linux-kernel, linux-rdma, Russell King
> > > With a SERDES component, the mapping becomes cleaner:
> > > component name supported
> > > MAC mac near-end
> > > SERDES serdes-ned near-end
> > > SERDES serdes-nea near-end
> > > SERDES serdes-fed far-end
> >
> > If Linux where to drive the SERDES, what part of Linux would it be?
> > Generic PHY? How does your SERDES hardware block fit into 802.3? Which
> > clause describes it?
>
> Hi Andrew,
>
> On OcteonTx2 SoC, the SerDes (GSERM) is a HW block integrated into the
> SoC die. It is not on an MDIO bus or any bus that Linux can enumerate.
> The block is fully managed by the firmware running on the SoC. The NIC
> driver configures it indirectly through firmware mailbox commands.
>
> The data path looks like:
> MAC (RPM) --- SerDes (GSERM) --- module/PHY
>
> In 802.3 terms, the closest match would be PMA. The GSERM handles
> serialization/deserialization and the analog front-end.
A Linux Generic PHY is probably also PMA.
802.3 says very little about SERDES, it is not a well defined term. So
i think we want PCS and PMA, not SERDES as a loopback point.
Andrew
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC net-next v2 0/6] ethtool: Generic loopback support
2026-03-11 12:32 ` Andrew Lunn
@ 2026-03-11 16:52 ` Russell King (Oracle)
0 siblings, 0 replies; 23+ messages in thread
From: Russell King (Oracle) @ 2026-03-11 16:52 UTC (permalink / raw)
To: Andrew Lunn
Cc: Naveen Mamindlapalli, Björn Töpel, netdev,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Saeed Mahameed, Tariq Toukan,
Leon Romanovsky, Andrew Lunn, Maxime Chevallier, Michael Chan,
Hariprasad Kelam, Ido Schimmel, Danielle Ratson, linux-kernel,
linux-rdma
On Wed, Mar 11, 2026 at 01:32:09PM +0100, Andrew Lunn wrote:
> > > > With a SERDES component, the mapping becomes cleaner:
> > > > component name supported
> > > > MAC mac near-end
> > > > SERDES serdes-ned near-end
> > > > SERDES serdes-nea near-end
> > > > SERDES serdes-fed far-end
> > >
> > > If Linux where to drive the SERDES, what part of Linux would it be?
> > > Generic PHY? How does your SERDES hardware block fit into 802.3? Which
> > > clause describes it?
> >
> > Hi Andrew,
> >
> > On OcteonTx2 SoC, the SerDes (GSERM) is a HW block integrated into the
> > SoC die. It is not on an MDIO bus or any bus that Linux can enumerate.
> > The block is fully managed by the firmware running on the SoC. The NIC
> > driver configures it indirectly through firmware mailbox commands.
> >
> > The data path looks like:
> > MAC (RPM) --- SerDes (GSERM) --- module/PHY
> >
> > In 802.3 terms, the closest match would be PMA. The GSERM handles
> > serialization/deserialization and the analog front-end.
>
> A Linux Generic PHY is probably also PMA.
>
> 802.3 says very little about SERDES, it is not a well defined term. So
> i think we want PCS and PMA, not SERDES as a loopback point.
That's a good point. I'm wondeirng whether to change "serdes" /
"SerDes" in my stmmac patches to be "pma".
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-03-11 16:52 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-09 14:16 ` Maxime Chevallier
2026-03-09 14:59 ` Björn Töpel
2026-03-09 16:45 ` Andrew Lunn
2026-03-10 10:23 ` Maxime Chevallier
2026-03-10 13:56 ` Andrew Lunn
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
2026-03-09 7:34 ` Maxime Chevallier
2026-03-09 8:21 ` Björn Töpel
2026-03-09 14:51 ` Björn Töpel
2026-03-09 16:14 ` Maxime Chevallier
2026-03-08 12:40 ` [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
2026-03-09 14:55 ` Björn Töpel
2026-03-10 7:35 ` Naveen Mamindlapalli
2026-03-10 14:00 ` Andrew Lunn
2026-03-11 5:59 ` Naveen Mamindlapalli
2026-03-11 12:32 ` Andrew Lunn
2026-03-11 16:52 ` Russell King (Oracle)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox