* [PATCH net-next v2 00/12] ethtool: Generic loopback support
@ 2026-03-25 14:50 Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration Björn Töpel
` (11 more replies)
0 siblings, 12 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Hi!
Background
==========
This series adds a generic ethtool loopback framework with GET/SET
netlink commands, using a component/name/id/depth model that
represents loopback points across the network path.
This is v2.
Design
======
The loopback model uses four axes to identify a loopback point:
1. Component (MAC, PHY, MODULE) -- identifies the Linux driver
boundary that owns the loopback. This maps to the Linux driver
model, not to 802.3 sublayers directly.
2. Name -- identifies the sublayer within the component using IEEE
802.3 vocabulary (e.g. "mac", "pcs", "pma", "pmd", "mii" for MAC
and PHY components; "cmis-host", "cmis-media" for MODULE). The
name is a free-form string to avoid over-constraining the enum,
but recommended names are documented with references to 802.3
clauses.
3. Id -- optional instance selector within a component type (e.g.
PHY index from phy_link_topology, port number). Defaults to 0
when there is only one instance.
4. Depth -- ordering index within a component instance. When a
component has multiple loopback points of the same type (e.g. two
PCS blocks inside a rate-adaptation PHY), depth distinguishes
them. Lower values are closer to the host, higher toward the
line/media. Defaults to 0 when there is only one loopback point
per (component, name) tuple.
Direction uses IEEE 802.3 terminology: "local" (host TX -> looped back
-> host RX, traffic originating from host returns to host) and
"remote" (line RX -> looped back -> line TX, traffic from far end
returns to far end). Direction is defined from the component's own
viewpoint -- this convention holds regardless of where the component
sits in the system topology.
Cross-component ordering comes from existing infrastructure (PHY link
topology, phy_index). Multi-netdev shared-resource loopbacks (QSGMII,
port breakout) are out of scope; drivers should only expose loopbacks
they can scope to a single netdev.
A filtered DUMP (with a dev-index in the header) lists all loopback
entries for that netdev; an unfiltered DUMP iterates over all netdevs.
The doit handler supports exact lookup by (component, name, id).
V2 design rationale
===================
Why keep name as a string for now? The review discussed replacing name
with an enum. I decided not to do that in v2.
Pros:
- enforces more consistency across drivers
- makes userspace parsing simpler
- makes selftests stricter
- keeps the UAPI taxonomy explicit
Cons:
- freezes the stage taxonomy before we fully understand the MAC side
- risks forcing unlike hardware points into the same enum bucket
- makes later extension more expensive
- may standardize the spelling without standardizing the actual
semantics
This series still does not attempt to model the full datapath as a DAG
or expose a generic topology dump. That would likely be useful for
future diagnostics, traffic generators, and deeper validation, but it
also looks like a larger design problem than loopback itself.
The intent of v2 is not to solve that larger problem now, but also not
to block it:
- component gives the Linux ownership boundary
- name keeps room for stage-specific vocabulary
- id selects the component instance
- depth orders loopback points within that instance
If a richer topology API is needed later, it should be additive rather
than requiring this loopback API to be replaced.
Oleksij's more topology-heavy use cases are therefore not fully solved
by this series, but the current model should still be extendable for
them.
CMIS support
============
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) -- local
- Host Side Output (Tx->Rx) -- remote
- Media Side Input (Rx->Tx) -- local
- Media Side Output (Tx->Rx) -- remote
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.
Currently, only mellanox/mlxsw, and broadcom/bnxt support CMIS
operations. I'll follow-up with mlx5 support.
Implementation
==============
Patch 1/12 ethtool: Add dump_one_dev callback for per-device sub-iteration
Adds the dump_one_dev callback to ethnl_request_ops, the ifindex and
pos_sub fields to ethnl_dump_ctx, and the dispatch logic in
ethnl_default_start() and ethnl_default_dumpit(). No functional
change; no command uses dump_one_dev yet.
Patch 2/12 ethtool: Convert per-PHY commands to dump_one_dev
Converts PSE, PLCA, PHY, and MSE commands from the separate
ethnl_perphy_{start,dumpit,done} handlers to use the generic
dump_one_dev callback via a shared ethnl_perphy_dump_one_dev()
function.
Patch 3/12 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, depth, supported directions
bitmask, and current direction.
Patch 4/12 ethtool: Add loopback GET/SET netlink implementation
Implements GET/SET dispatch in a new loopback.c. GET uses the
dump_one_dev infrastructure for dump enumeration (by flat index)
and supports doit exact lookup by (component, id, name) via
parse_request. SET switches on the component and calls the right
handler per entry. No components are wired yet.
Patch 5/12 ethtool: Add CMIS loopback helpers for module loopback control
Adds cmis_loopback.c with the MODULE component handlers and wires
them into loopback.c's dispatch. GET enumerates entries by index
(ethtool_cmis_get_loopback_by_index) or looks up by name
(ethtool_cmis_get_loopback). SET (ethtool_cmis_set_loopback)
resolves name to control byte indices and enforces mutual
exclusivity.
Patch 6/12 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 7/12 ethtool: Add MAC loopback support via ethtool_ops
Extends struct ethtool_ops with three loopback callbacks for
driver-level MAC loopback: get_loopback (exact lookup by name/id),
get_loopback_by_index (dump enumeration), and set_loopback. Wires
the MAC component into loopback.c's dispatch. For dump enumeration,
MAC entries are tried first, then MODULE/CMIS entries follow at the
next index offset.
Patch 8/12 netdevsim: Add MAC loopback simulation
Implements the three ethtool loopback ops in netdevsim. Exposes a
single MAC loopback entry ("mac") with both local and remote
support. State is stored in memory and exposed via debugfs under
ethtool/mac_lb/{supported,direction}.
Patch 9/12 selftests: drv-net: Add MAC loopback netdevsim test
Adds loopback_nsim.py with netdevsim-specific tests for MAC
loopback: entry presence, SET/GET round-trip with debugfs
verification, and error paths.
Patch 10/12 MAINTAINERS: Add entry for ethtool loopback
Adds a MAINTAINERS entry for the ethtool loopback subsystem covering
the core loopback and CMIS loopback netlink implementation, and the
associated selftests.
Patch 11/12 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.
Patch 12/12 selftests: drv-net: Add CMIS loopback netdevsim test
Extends loopback_nsim.py with netdevsim-specific tests that seed the
EEPROM via debugfs: capability reporting, EEPROM byte verification,
combined MAC + MODULE dump, and error paths.
Changes since v1
================
- Split dump_one_dev infrastructure patch into two: one adding the
generic callback infrastructure, one converting per-PHY commands
to use it. (Jakub)
- Used Jakub's suggested pattern for filtered dumps: initialize both
ifindex and pos_ifindex in _start(), then break early in the loop
when pos_ifindex diverges, avoiding a separate single-device code
path. (Jakub)
- Dropped PCS as a top-level component. Components now map to Linux
driver boundaries: MAC, PHY, MODULE. Sublayer granularity (pcs,
pma, pmd, mii) lives in the name attribute using IEEE 802.3
vocabulary. (Maxime)
- Renamed near-end/far-end to local/remote per IEEE 802.3
terminology. (Maxime)
- Added depth field to ETHTOOL_A_LOOPBACK_ENTRY for ordering
multiple loopback points within a single component instance (e.g.
rate-adaptation PHY with two PCS blocks). (Jakub)
- Documented the viewpoint convention: direction is always from the
component's own perspective, local = toward host, remote = toward
line, regardless of system topology. (Oleksij)
- Expanded component doc strings to explain that the name attribute
uses IEEE 802.3 sublayer vocabulary. (Andrew, Maxime)
Changes since RFC v2
====================
- Switched LOOPBACK_GET from doit-with-array to dumpit, where each
loopback entry is a separate netlink message. Uses the new generic
dump_one_dev sub-iterator infrastructure instead of duplicating the
perphy dump pattern. (Maxime)
- u32 to u8 to represent the enums in the YAML. (Maxime)
- Tried to document the YAML better. (Andrew)
- Added doit exact lookup by (component, id, name) via
parse_request, so single-entry GET doesn't need a flat index.
- Added MAC loopback support via three new ethtool_ops callbacks
(get_loopback(), get_loopback_by_index(), set_loopback()) with
netdevsim implementation and tests.
- Added MAINTAINERS entry.
Opens/limitations
=================
- mlx5 CMIS support is still not part of the series.
- PHY loopback is defined in the UAPI but not yet implemented.
- No per-lane support -- loopback is all-or-nothing (0xff/0x00)
across lanes.
Related work
============
[1] Generic loopback support, v1
https://lore.kernel.org/netdev/20260310104743.907818-1-bjorn@kernel.org/
[2] Generic loopback support, RFC v2
https://lore.kernel.org/netdev/20260219130050.2390226-1-bjorn@kernel.org/
[3] New loopback modes
https://lore.kernel.org/netdev/20251024044849.1098222-1-hkelam@marvell.com/
[4] PHY loopback
https://lore.kernel.org/netdev/20240911212713.2178943-1-maxime.chevallier@bootlin.com/
[5] bnxt_en: add .set_module_eeprom_by_page() support
https://lore.kernel.org/netdev/20250310183129.3154117-8-michael.chan@broadcom.com/
[6] net/mlx5e: Implement set_module_eeprom_by_page ethtool callback
https://lore.kernel.org/netdev/20260219130050.2390226-5-bjorn@kernel.org/
Björn Töpel (12):
ethtool: Add dump_one_dev callback for per-device sub-iteration
ethtool: Convert per-PHY commands to dump_one_dev
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
ethtool: Add MAC loopback support via ethtool_ops
netdevsim: Add MAC loopback simulation
selftests: drv-net: Add MAC loopback netdevsim test
MAINTAINERS: Add entry for ethtool loopback
netdevsim: Add module EEPROM simulation via debugfs
selftests: drv-net: Add CMIS loopback netdevsim test
Documentation/netlink/specs/ethtool.yaml | 142 ++++++
MAINTAINERS | 6 +
drivers/net/netdevsim/ethtool.c | 147 +++++++
drivers/net/netdevsim/netdevsim.h | 15 +
include/linux/ethtool.h | 28 ++
.../uapi/linux/ethtool_netlink_generated.h | 67 +++
net/ethtool/Makefile | 2 +-
net/ethtool/cmis_loopback.c | 407 ++++++++++++++++++
net/ethtool/loopback.c | 351 +++++++++++++++
net/ethtool/mse.c | 1 +
net/ethtool/netlink.c | 284 ++++--------
net/ethtool/netlink.h | 49 +++
net/ethtool/phy.c | 1 +
net/ethtool/plca.c | 2 +
net/ethtool/pse-pd.c | 1 +
.../testing/selftests/drivers/net/hw/Makefile | 2 +
.../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++
.../selftests/drivers/net/hw/loopback_nsim.py | 343 +++++++++++++++
18 files changed, 1865 insertions(+), 210 deletions(-)
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: d1e59a46973719e458bec78d00dd767d7a7ba71f
--
2.53.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 18:20 ` Maxime Chevallier
2026-03-25 14:50 ` [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev Björn Töpel
` (10 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Add the dump_one_dev callback to ethnl_request_ops, allowing commands
to provide custom per-device dump logic with sub-positioning. Extend
ethnl_dump_ctx with ifindex and pos_sub fields.
No functional change; no command uses dump_one_dev yet.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
net/ethtool/netlink.c | 66 ++++++++++++++++++-------------------------
net/ethtool/netlink.h | 31 ++++++++++++++++++++
2 files changed, 58 insertions(+), 39 deletions(-)
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 5046023a30b1..8d161f0882d0 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -346,36 +346,6 @@ int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
/* GET request helpers */
-/**
- * struct ethnl_dump_ctx - context structure for generic dumpit() callback
- * @ops: request ops of currently processed message type
- * @req_info: parsed request header of processed request
- * @reply_data: data needed to compose the reply
- * @pos_ifindex: saved iteration position - ifindex
- *
- * These parameters are kept in struct netlink_callback as context preserved
- * between iterations. They are initialized by ethnl_default_start() and used
- * in ethnl_default_dumpit() and ethnl_default_done().
- */
-struct ethnl_dump_ctx {
- const struct ethnl_request_ops *ops;
- struct ethnl_req_info *req_info;
- struct ethnl_reply_data *reply_data;
- unsigned long pos_ifindex;
-};
-
-/**
- * struct ethnl_perphy_dump_ctx - context for dumpit() PHY-aware callbacks
- * @ethnl_ctx: generic ethnl context
- * @ifindex: For Filtered DUMP requests, the ifindex of the targeted netdev
- * @pos_phyindex: iterator position for multi-msg DUMP
- */
-struct ethnl_perphy_dump_ctx {
- struct ethnl_dump_ctx ethnl_ctx;
- unsigned int ifindex;
- unsigned long pos_phyindex;
-};
-
static const struct ethnl_request_ops *
ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STRSET_GET] = ðnl_strset_request_ops,
@@ -618,6 +588,7 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
+ const struct genl_info *info = genl_info_dump(cb);
struct net *net = sock_net(skb->sk);
netdevice_tracker dev_tracker;
struct net_device *dev;
@@ -625,10 +596,20 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
rcu_read_lock();
for_each_netdev_dump(net, dev, ctx->pos_ifindex) {
+ if (ctx->ifindex && ctx->ifindex != ctx->pos_ifindex)
+ break;
+
netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
rcu_read_unlock();
- ret = ethnl_default_dump_one(skb, dev, ctx, genl_info_dump(cb));
+ if (ctx->ops->dump_one_dev) {
+ ctx->req_info->dev = dev;
+ ret = ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub,
+ info);
+ ctx->req_info->dev = NULL;
+ } else {
+ ret = ethnl_default_dump_one(skb, dev, ctx, info);
+ }
rcu_read_lock();
netdev_put(dev, &dev_tracker);
@@ -674,19 +655,26 @@ static int ethnl_default_start(struct netlink_callback *cb)
ret = ethnl_default_parse(req_info, &info->info, ops, false);
if (ret < 0)
goto free_reply_data;
- if (req_info->dev) {
- /* We ignore device specification in dump requests but as the
- * same parser as for non-dump (doit) requests is used, it
- * would take reference to the device if it finds one
- */
- netdev_put(req_info->dev, &req_info->dev_tracker);
- req_info->dev = NULL;
- }
ctx->ops = ops;
ctx->req_info = req_info;
ctx->reply_data = reply_data;
ctx->pos_ifindex = 0;
+ ctx->ifindex = 0;
+ ctx->pos_sub = 0;
+
+ if (req_info->dev) {
+ if (ops->dump_one_dev) {
+ /* Sub-iterator dumps keep track of the dev's ifindex
+ * so the dumpit handler can grab/release the netdev
+ * per iteration.
+ */
+ ctx->ifindex = req_info->dev->ifindex;
+ ctx->pos_ifindex = ctx->ifindex;
+ }
+ netdev_put(req_info->dev, &req_info->dev_tracker);
+ req_info->dev = NULL;
+ }
return 0;
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index aaf6f2468768..e01adc5db02f 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -10,6 +10,28 @@
struct ethnl_req_info;
+/**
+ * struct ethnl_dump_ctx - context structure for generic dumpit() callback
+ * @ops: request ops of currently processed message type
+ * @req_info: parsed request header of processed request
+ * @reply_data: data needed to compose the reply
+ * @pos_ifindex: saved iteration position - ifindex
+ * @ifindex: for filtered dump requests, the ifindex of the targeted netdev
+ * @pos_sub: iterator position for per-device iteration
+ *
+ * These parameters are kept in struct netlink_callback as context preserved
+ * between iterations. They are initialized by ethnl_default_start() and used
+ * in ethnl_default_dumpit() and ethnl_default_done().
+ */
+struct ethnl_dump_ctx {
+ const struct ethnl_request_ops *ops;
+ struct ethnl_req_info *req_info;
+ struct ethnl_reply_data *reply_data;
+ unsigned long pos_ifindex;
+ unsigned int ifindex;
+ unsigned long pos_sub;
+};
+
u32 ethnl_bcast_seq_next(void);
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
@@ -365,6 +387,10 @@ int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
* used e.g. to free any additional data structures outside the main
* structure which were allocated by ->prepare_data(). When processing
* dump requests, ->cleanup() is called for each message.
+ * @dump_one_dev:
+ * Optional callback for dumping data for a single device. When set,
+ * overrides the default dump behavior for GET requests, allowing
+ * per-device iteration with sub-positioning via @pos_sub.
* @set_validate:
* Check if set operation is supported for a given device, and perform
* extra input checks. Expected return values:
@@ -409,6 +435,11 @@ struct ethnl_request_ops {
const struct ethnl_reply_data *reply_data);
void (*cleanup_data)(struct ethnl_reply_data *reply_data);
+ int (*dump_one_dev)(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info);
+
int (*set_validate)(struct ethnl_req_info *req_info,
struct genl_info *info);
int (*set)(struct ethnl_req_info *req_info,
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 18:21 ` Maxime Chevallier
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
` (9 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Convert PSE, PLCA, PHY, and MSE commands from the separate
ethnl_perphy_{start,dumpit,done} handlers to use the generic
dump_one_dev callback. This removes the per-PHY specific dump
infrastructure (ethnl_perphy_dump_ctx, ethnl_perphy_dump_context,
ethnl_perphy_start, ethnl_perphy_dumpit, ethnl_perphy_done, and the
internal helpers) in favor of a shared ethnl_perphy_dump_one_dev()
function.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
net/ethtool/mse.c | 1 +
net/ethtool/netlink.c | 194 ++++++------------------------------------
net/ethtool/netlink.h | 4 +
net/ethtool/phy.c | 1 +
net/ethtool/plca.c | 2 +
net/ethtool/pse-pd.c | 1 +
6 files changed, 35 insertions(+), 168 deletions(-)
diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c
index e91b74430f76..3f33182283ce 100644
--- a/net/ethtool/mse.c
+++ b/net/ethtool/mse.c
@@ -325,4 +325,5 @@ const struct ethnl_request_ops ethnl_mse_request_ops = {
.cleanup_data = mse_cleanup_data,
.reply_size = mse_reply_size,
.fill_reply = mse_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 8d161f0882d0..edeeca67918a 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -399,12 +399,6 @@ static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
return (struct ethnl_dump_ctx *)cb->ctx;
}
-static struct ethnl_perphy_dump_ctx *
-ethnl_perphy_dump_context(struct netlink_callback *cb)
-{
- return (struct ethnl_perphy_dump_ctx *)cb->ctx;
-}
-
/**
* ethnl_default_parse() - Parse request message
* @req_info: pointer to structure to put data into
@@ -686,169 +680,33 @@ static int ethnl_default_start(struct netlink_callback *cb)
return ret;
}
-/* per-PHY ->start() handler for GET requests */
-static int ethnl_perphy_start(struct netlink_callback *cb)
+/* Shared dump_one_dev for per-PHY commands (PSE, PLCA, PHY, MSE) */
+int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info)
{
- struct ethnl_perphy_dump_ctx *phy_ctx = ethnl_perphy_dump_context(cb);
- const struct genl_dumpit_info *info = genl_dumpit_info(cb);
- struct ethnl_dump_ctx *ctx = &phy_ctx->ethnl_ctx;
- struct ethnl_reply_data *reply_data;
- const struct ethnl_request_ops *ops;
- struct ethnl_req_info *req_info;
- struct genlmsghdr *ghdr;
- int ret;
-
- BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
-
- ghdr = nlmsg_data(cb->nlh);
- ops = ethnl_default_requests[ghdr->cmd];
- if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd))
- return -EOPNOTSUPP;
- req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
- if (!req_info)
- return -ENOMEM;
- reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
- if (!reply_data) {
- ret = -ENOMEM;
- goto free_req_info;
- }
-
- /* Unlike per-dev dump, don't ignore dev. The dump handler
- * will notice it and dump PHYs from given dev. We only keep track of
- * the dev's ifindex, .dumpit() will grab and release the netdev itself.
- */
- ret = ethnl_default_parse(req_info, &info->info, ops, false);
- if (ret < 0)
- goto free_reply_data;
- if (req_info->dev) {
- phy_ctx->ifindex = req_info->dev->ifindex;
- netdev_put(req_info->dev, &req_info->dev_tracker);
- req_info->dev = NULL;
- }
-
- ctx->ops = ops;
- ctx->req_info = req_info;
- ctx->reply_data = reply_data;
- ctx->pos_ifindex = 0;
-
- return 0;
-
-free_reply_data:
- kfree(reply_data);
-free_req_info:
- kfree(req_info);
-
- return ret;
-}
-
-static int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
- struct ethnl_perphy_dump_ctx *ctx,
- const struct genl_info *info)
-{
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- struct net_device *dev = ethnl_ctx->req_info->dev;
+ struct net_device *dev = ctx->req_info->dev;
struct phy_device_node *pdn;
int ret;
if (!dev->link_topo)
return 0;
- xa_for_each_start(&dev->link_topo->phys, ctx->pos_phyindex, pdn,
- ctx->pos_phyindex) {
- ethnl_ctx->req_info->phy_index = ctx->pos_phyindex;
+ xa_for_each_start(&dev->link_topo->phys, *pos_sub, pdn,
+ *pos_sub) {
+ ctx->req_info->phy_index = *pos_sub;
/* We can re-use the original dump_one as ->prepare_data in
* commands use ethnl_req_get_phydev(), which gets the PHY from
* the req_info->phy_index
*/
- ret = ethnl_default_dump_one(skb, dev, ethnl_ctx, info);
+ ret = ethnl_default_dump_one(skb, dev, ctx, info);
if (ret)
return ret;
}
- ctx->pos_phyindex = 0;
-
- return 0;
-}
-
-static int ethnl_perphy_dump_all_dev(struct sk_buff *skb,
- struct ethnl_perphy_dump_ctx *ctx,
- const struct genl_info *info)
-{
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- struct net *net = sock_net(skb->sk);
- netdevice_tracker dev_tracker;
- struct net_device *dev;
- int ret = 0;
-
- rcu_read_lock();
- for_each_netdev_dump(net, dev, ethnl_ctx->pos_ifindex) {
- netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
- rcu_read_unlock();
-
- /* per-PHY commands use ethnl_req_get_phydev(), which needs the
- * net_device in the req_info
- */
- ethnl_ctx->req_info->dev = dev;
- ret = ethnl_perphy_dump_one_dev(skb, ctx, info);
-
- rcu_read_lock();
- netdev_put(dev, &dev_tracker);
- ethnl_ctx->req_info->dev = NULL;
-
- if (ret < 0 && ret != -EOPNOTSUPP) {
- if (likely(skb->len))
- ret = skb->len;
- break;
- }
- ret = 0;
- }
- rcu_read_unlock();
-
- return ret;
-}
-
-/* per-PHY ->dumpit() handler for GET requests. */
-static int ethnl_perphy_dumpit(struct sk_buff *skb,
- struct netlink_callback *cb)
-{
- struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
- const struct genl_dumpit_info *info = genl_dumpit_info(cb);
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
- int ret = 0;
-
- if (ctx->ifindex) {
- netdevice_tracker dev_tracker;
- struct net_device *dev;
-
- dev = netdev_get_by_index(genl_info_net(&info->info),
- ctx->ifindex, &dev_tracker,
- GFP_KERNEL);
- if (!dev)
- return -ENODEV;
-
- ethnl_ctx->req_info->dev = dev;
- ret = ethnl_perphy_dump_one_dev(skb, ctx, genl_info_dump(cb));
-
- if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
- ret = skb->len;
-
- netdev_put(dev, &dev_tracker);
- } else {
- ret = ethnl_perphy_dump_all_dev(skb, ctx, genl_info_dump(cb));
- }
-
- return ret;
-}
-
-/* per-PHY ->done() handler for GET requests */
-static int ethnl_perphy_done(struct netlink_callback *cb)
-{
- struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
- struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
-
- kfree(ethnl_ctx->reply_data);
- kfree(ethnl_ctx->req_info);
+ *pos_sub = 0;
return 0;
}
@@ -1410,9 +1268,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PSE_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_pse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1,
},
@@ -1434,9 +1292,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PLCA_GET_CFG,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_plca_get_cfg_policy,
.maxattr = ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1,
},
@@ -1450,9 +1308,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PLCA_GET_STATUS,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_plca_get_status_policy,
.maxattr = ARRAY_SIZE(ethnl_plca_get_status_policy) - 1,
},
@@ -1482,9 +1340,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_PHY_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_phy_get_policy,
.maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1,
},
@@ -1528,9 +1386,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_MSE_GET,
.doit = ethnl_default_doit,
- .start = ethnl_perphy_start,
- .dumpit = ethnl_perphy_dumpit,
- .done = ethnl_perphy_done,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
.policy = ethnl_mse_get_policy,
.maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
},
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index e01adc5db02f..dda2f5593ed9 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -546,6 +546,10 @@ 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 ethnl_perphy_dump_one_dev(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info);
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];
diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c
index d4e6887055ab..4bb23a5d6ad5 100644
--- a/net/ethtool/phy.c
+++ b/net/ethtool/phy.c
@@ -162,4 +162,5 @@ const struct ethnl_request_ops ethnl_phy_request_ops = {
.reply_size = phy_reply_size,
.fill_reply = phy_fill_reply,
.cleanup_data = phy_cleanup_data,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/plca.c b/net/ethtool/plca.c
index 91f0c4233298..84e902532617 100644
--- a/net/ethtool/plca.c
+++ b/net/ethtool/plca.c
@@ -188,6 +188,7 @@ const struct ethnl_request_ops ethnl_plca_cfg_request_ops = {
.prepare_data = plca_get_cfg_prepare_data,
.reply_size = plca_get_cfg_reply_size,
.fill_reply = plca_get_cfg_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
.set = ethnl_set_plca,
.set_ntf_cmd = ETHTOOL_MSG_PLCA_NTF,
@@ -268,4 +269,5 @@ const struct ethnl_request_ops ethnl_plca_status_request_ops = {
.prepare_data = plca_get_status_prepare_data,
.reply_size = plca_get_status_reply_size,
.fill_reply = plca_get_status_fill_reply,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
};
diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
index 2eb9bdc2dcb9..83f0205053a3 100644
--- a/net/ethtool/pse-pd.c
+++ b/net/ethtool/pse-pd.c
@@ -338,6 +338,7 @@ const struct ethnl_request_ops ethnl_pse_request_ops = {
.reply_size = pse_reply_size,
.fill_reply = pse_fill_reply,
.cleanup_data = pse_cleanup_data,
+ .dump_one_dev = ethnl_perphy_dump_one_dev,
.set = ethnl_set_pse,
/* PSE has no notification */
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-26 8:10 ` Maxime Chevallier
` (2 more replies)
2026-03-25 14:50 ` [PATCH net-next v2 04/12] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
` (8 subsequent siblings)
11 siblings, 3 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Add the netlink YAML spec and auto-generated UAPI header for a unified
loopback interface covering MAC, PHY, and pluggable module components.
Each loopback point is described by a nested entry attribute
containing:
- component where in the path (MAC, PHY, MODULE)
- name subsystem label, e.g. "cmis-host" or "cmis-media"
- id optional instance selector (e.g. PHY id, port id)
- depth ordering index within a component (0 = first/only)
- supported bitmask of supported directions
- direction LOCAL, REMOTE, or 0 (disabled)
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
Documentation/netlink/specs/ethtool.yaml | 142 ++++++++++++++++++
.../uapi/linux/ethtool_netlink_generated.h | 67 +++++++++
2 files changed, 209 insertions(+)
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 5dd4d1b5d94b..fe7e644d1b10 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -211,6 +211,58 @@ 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 component loopback. Covers loopback points owned by the
+ MAC / network-controller driver, including the MAC block itself,
+ any MAC-side PCS, and SoC SerDes. The name attribute identifies
+ the sublayer using IEEE 802.3 vocabulary (e.g. mac, pcs, mii).
+ -
+ name: phy
+ doc: |
+ Ethernet PHY loopback. Covers loopback points inside the
+ Ethernet PHY managed by phylib, such as the PHY-internal PCS,
+ PMA, or PMD. The name attribute identifies the sublayer using
+ IEEE 802.3 vocabulary (e.g. pcs, pma, pmd, mii). A Base-T SFP
+ module containing an Ethernet PHY driven by Linux should report
+ loopback under this component, not module.
+ -
+ name: module
+ doc: |
+ Pluggable module (e.g. CMIS (Q)SFP) loopback. Covers loopback
+ modes controlled via module firmware or EEPROM registers. When
+ Linux drives an Ethernet PHY inside the module via phylib, use
+ the phy component instead.
+ -
+ name: loopback-direction
+ type: flags
+ doc: |
+ Loopback direction flags. Direction is defined from the
+ component's own viewpoint: local loops traffic originating from
+ the host back to the host, remote loops traffic arriving from
+ the line back toward the line. This convention holds regardless
+ of where the component sits in the system topology. Used as a
+ bitmask in supported, and as a single value in direction.
+ entries:
+ -
+ name: local
+ doc: |
+ Local loopback (IEEE 802.3). Host TX -> looped back -> host
+ RX (traffic originating from host returns to host).
+ -
+ name: remote
+ doc: |
+ Remote loopback (IEEE 802.3). Line RX -> looped back -> line
+ TX (traffic from far end returns to far end).
+
attribute-sets:
-
name: header
@@ -1905,6 +1957,68 @@ 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: name
+ type: string
+ doc: |
+ Subsystem-specific name for the loopback point within the
+ component.
+ -
+ name: id
+ type: u32
+ doc: Optional component instance identifier.
+ -
+ name: depth
+ type: u8
+ doc: |
+ Ordering index within a component instance. When a component
+ has multiple loopback points of the same type (e.g. two PCS
+ blocks inside a rate-adaptation PHY), depth distinguishes
+ them. Lower depth values are closer to the host side, higher
+ values are closer to the line/media side. Defaults to 0 when
+ there is only one loopback point per (component, name) tuple.
+ -
+ name: supported
+ type: u8
+ enum: loopback-direction
+ enum-as-flags: true
+ doc: Bitmask of supported loopback directions
+ -
+ name: direction
+ type: u8
+ 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
@@ -2859,6 +2973,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 8134baf7860f..31f9178f5b62 100644
--- a/include/uapi/linux/ethtool_netlink_generated.h
+++ b/include/uapi/linux/ethtool_netlink_generated.h
@@ -78,6 +78,47 @@ 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 component loopback. Covers loopback
+ * points owned by the MAC / network-controller driver, including the MAC
+ * block itself, any MAC-side PCS, and SoC SerDes. The name attribute
+ * identifies the sublayer using IEEE 802.3 vocabulary (e.g. mac, pcs, mii).
+ * @ETHTOOL_LOOPBACK_COMPONENT_PHY: Ethernet PHY loopback. Covers loopback
+ * points inside the Ethernet PHY managed by phylib, such as the PHY-internal
+ * PCS, PMA, or PMD. The name attribute identifies the sublayer using IEEE
+ * 802.3 vocabulary (e.g. pcs, pma, pmd, mii). A Base-T SFP module containing
+ * an Ethernet PHY driven by Linux should report loopback under this
+ * component, not module.
+ * @ETHTOOL_LOOPBACK_COMPONENT_MODULE: Pluggable module (e.g. CMIS (Q)SFP)
+ * loopback. Covers loopback modes controlled via module firmware or EEPROM
+ * registers. When Linux drives an Ethernet PHY inside the module via phylib,
+ * use the phy component instead.
+ */
+enum ethtool_loopback_component {
+ ETHTOOL_LOOPBACK_COMPONENT_MAC,
+ ETHTOOL_LOOPBACK_COMPONENT_PHY,
+ ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+};
+
+/**
+ * enum ethtool_loopback_direction - Loopback direction flags. Direction is
+ * defined from the component's own viewpoint: local loops traffic
+ * originating from the host back to the host, remote loops traffic arriving
+ * from the line back toward the line. This convention holds regardless of
+ * where the component sits in the system topology. Used as a bitmask in
+ * supported, and as a single value in direction.
+ * @ETHTOOL_LOOPBACK_DIRECTION_LOCAL: Local loopback (IEEE 802.3). Host TX ->
+ * looped back -> host RX (traffic originating from host returns to host).
+ * @ETHTOOL_LOOPBACK_DIRECTION_REMOTE: Remote loopback (IEEE 802.3). Line RX ->
+ * looped back -> line TX (traffic from far end returns to far end).
+ */
+enum ethtool_loopback_direction {
+ ETHTOOL_LOOPBACK_DIRECTION_LOCAL = 1,
+ ETHTOOL_LOOPBACK_DIRECTION_REMOTE = 2,
+};
+
enum {
ETHTOOL_A_HEADER_UNSPEC,
ETHTOOL_A_HEADER_DEV_INDEX,
@@ -840,6 +881,28 @@ 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_NAME,
+ ETHTOOL_A_LOOPBACK_ENTRY_ID,
+ ETHTOOL_A_LOOPBACK_ENTRY_DEPTH,
+ 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,
@@ -893,6 +956,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)
@@ -954,6 +1019,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
* [PATCH net-next v2 04/12] ethtool: Add loopback GET/SET netlink implementation
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (2 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 05/12] ethtool: Add CMIS loopback helpers for module loopback control Björn Töpel
` (7 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
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 | 18 +++
net/ethtool/Makefile | 2 +-
net/ethtool/loopback.c | 315 ++++++++++++++++++++++++++++++++++++++++
net/ethtool/netlink.c | 24 ++-
net/ethtool/netlink.h | 6 +
5 files changed, 362 insertions(+), 3 deletions(-)
create mode 100644 net/ethtool/loopback.c
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 1cb0740ba331..81a9c186564d 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -860,6 +860,24 @@ 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
+ * @component: Loopback component
+ * @name: Subsystem-specific name for the loopback point
+ * @id: Optional component instance identifier, 0 means not specified
+ * @depth: Ordering index within a component instance, 0 means first/only
+ * @supported: Bitmask of supported directions
+ * @direction: Current loopback direction, 0 means disabled
+ */
+struct ethtool_loopback_entry {
+ enum ethtool_loopback_component component;
+ char name[ETH_GSTRING_LEN];
+ u32 id;
+ u8 depth;
+ u8 supported;
+ u8 direction;
+};
+
/**
* 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..03b86662dc0f
--- /dev/null
+++ b/net/ethtool/loopback.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "netlink.h"
+#include "common.h"
+
+struct loopback_req_info {
+ struct ethnl_req_info base;
+ enum ethtool_loopback_component component;
+ u32 id;
+ char name[ETH_GSTRING_LEN];
+ bool lookup_by_name;
+ u32 index;
+};
+
+#define LOOPBACK_REQINFO(__req_base) \
+ container_of(__req_base, struct loopback_req_info, base)
+
+struct loopback_reply_data {
+ struct ethnl_reply_data base;
+ struct ethtool_loopback_entry entry;
+};
+
+#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 },
+ [ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION] =
+ NLA_POLICY_MASK(NLA_U8, ETHTOOL_LOOPBACK_DIRECTION_LOCAL |
+ ETHTOOL_LOOPBACK_DIRECTION_REMOTE),
+ [ETHTOOL_A_LOOPBACK_ENTRY_DEPTH] = { .type = NLA_U8 },
+};
+
+const struct nla_policy
+ethnl_loopback_get_policy[ETHTOOL_A_LOOPBACK_MAX + 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_request(struct ethnl_req_info *req_base,
+ const struct genl_info *info,
+ struct nlattr **tb,
+ struct netlink_ext_ack *extack)
+{
+ struct loopback_req_info *req_info = LOOPBACK_REQINFO(req_base);
+ struct nlattr *entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1];
+ int ret;
+
+ if (!tb[ETHTOOL_A_LOOPBACK_ENTRY])
+ return 0;
+
+ ret = nla_parse_nested(entry_tb, ETHTOOL_A_LOOPBACK_ENTRY_MAX,
+ tb[ETHTOOL_A_LOOPBACK_ENTRY],
+ ethnl_loopback_entry_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ if (!entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT] ||
+ !entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME]) {
+ NL_SET_ERR_MSG(extack,
+ "component and name required for loopback lookup");
+ return -EINVAL;
+ }
+
+ req_info->component =
+ nla_get_u32(entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]);
+ if (entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_ID])
+ req_info->id =
+ nla_get_u32(entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]);
+ nla_strscpy(req_info->name, entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME],
+ sizeof(req_info->name));
+ req_info->lookup_by_name = true;
+
+ return 0;
+}
+
+static int loopback_get(struct net_device *dev,
+ enum ethtool_loopback_component component, u32 id,
+ const char *name,
+ struct ethtool_loopback_entry *entry)
+{
+ switch (component) {
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int loopback_get_by_index(struct net_device *dev, u32 index,
+ struct ethtool_loopback_entry *entry)
+{
+ return -EOPNOTSUPP;
+}
+
+static int loopback_prepare_data(const struct ethnl_req_info *req_base,
+ struct ethnl_reply_data *reply_base,
+ const struct genl_info *info)
+{
+ const struct loopback_req_info *req_info = LOOPBACK_REQINFO(req_base);
+ 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;
+
+ if (req_info->lookup_by_name)
+ ret = loopback_get(dev, req_info->component, req_info->id,
+ req_info->name, &data->entry);
+ else
+ ret = loopback_get_by_index(dev, req_info->index, &data->entry);
+
+ 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)
+{
+ return nla_total_size(0) + /* nest */
+ nla_total_size(sizeof(u32)) + /* component */
+ nla_total_size(sizeof(u32)) + /* id */
+ nla_total_size(sizeof(u8)) + /* supported */
+ nla_total_size(sizeof(u8)) + /* direction */
+ nla_total_size(sizeof(u8)) + /* depth */
+ nla_total_size(ETH_GSTRING_LEN); /* name */
+}
+
+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_entry *entry = &data->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 (entry->depth &&
+ nla_put_u8(skb, ETHTOOL_A_LOOPBACK_ENTRY_DEPTH, entry->depth))
+ goto err_cancel;
+
+ if (nla_put_u8(skb, ETHTOOL_A_LOOPBACK_ENTRY_SUPPORTED,
+ entry->supported) ||
+ nla_put_u8(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;
+}
+
+/* SET */
+
+const struct nla_policy
+ethnl_loopback_set_policy[ETHTOOL_A_LOOPBACK_MAX + 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_DEPTH])
+ entry->depth = nla_get_u8(tb[ETHTOOL_A_LOOPBACK_ENTRY_DEPTH]);
+
+ 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_u8(tb[ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION]);
+
+ return 0;
+}
+
+static int __loopback_set(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ switch (entry->component) {
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int loopback_set(struct ethnl_req_info *req_info,
+ struct genl_info *info)
+{
+ struct net_device *dev = req_info->dev;
+ struct ethtool_loopback_entry entry;
+ int rem, ret, mod = 0;
+ struct nlattr *attr;
+ bool found = false;
+
+ nla_for_each_attr(attr, genlmsg_data(info->genlhdr),
+ genlmsg_len(info->genlhdr), rem) {
+ if (nla_type(attr) != ETHTOOL_A_LOOPBACK_ENTRY)
+ continue;
+
+ found = true;
+ memset(&entry, 0, sizeof(entry));
+ ret = loopback_parse_entry(attr, &entry, info->extack);
+ if (ret < 0)
+ return ret;
+
+ ret = __loopback_set(dev, &entry, info->extack);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ mod = 1;
+ }
+
+ if (!found) {
+ NL_SET_ERR_MSG(info->extack, "no loopback entries specified");
+ return -EINVAL;
+ }
+
+ return mod;
+}
+
+static int loopback_dump_one_dev(struct sk_buff *skb,
+ struct ethnl_dump_ctx *ctx,
+ unsigned long *pos_sub,
+ const struct genl_info *info)
+{
+ struct loopback_req_info *req_info =
+ container_of(ctx->req_info, struct loopback_req_info, base);
+ int ret;
+
+ for (;; (*pos_sub)++) {
+ req_info->index = *pos_sub;
+ ret = ethnl_default_dump_one(skb, ctx->req_info->dev, ctx,
+ info);
+ if (ret == -EOPNOTSUPP)
+ break;
+ if (ret)
+ return ret;
+ }
+
+ *pos_sub = 0;
+
+ return 0;
+}
+
+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),
+
+ .parse_request = loopback_parse_request,
+ .prepare_data = loopback_prepare_data,
+ .reply_size = loopback_reply_size,
+ .fill_reply = loopback_fill_reply,
+ .dump_one_dev = loopback_dump_one_dev,
+
+ .set = loopback_set,
+ .set_ntf_cmd = ETHTOOL_MSG_LOOPBACK_NTF,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index edeeca67918a..d542978a683e 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -392,6 +392,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)
@@ -539,8 +541,8 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
return ret;
}
-static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
- const struct ethnl_dump_ctx *ctx,
+int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
+ const struct ethnl_dump_ctx *ctx,
const struct genl_info *info)
{
void *ehdr;
@@ -810,6 +812,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 */
@@ -918,6 +921,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,
@@ -1392,6 +1396,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 dda2f5593ed9..693df12eef63 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -475,6 +475,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];
@@ -531,6 +532,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_MAX + 1];
+extern const struct nla_policy ethnl_loopback_set_policy[ETHTOOL_A_LOOPBACK_MAX + 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);
@@ -546,6 +549,9 @@ 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 ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
+ const struct ethnl_dump_ctx *ctx,
+ const struct genl_info *info);
int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
struct ethnl_dump_ctx *ctx,
unsigned long *pos_sub,
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 05/12] ethtool: Add CMIS loopback helpers for module loopback control
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (3 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 04/12] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 06/12] selftests: drv-net: Add loopback driver test Björn Töpel
` (6 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
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_get_loopback_by_index(): used to enumerate what the
module supports.
- ethtool_cmis_set_loopback(): 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 local to remote
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", local -> Host Side Input (Byte 183)
- MODULE, "cmis-host", remote -> Host Side Output (Byte 182)
- MODULE, "cmis-media", local -> Media Side Input (Byte 181)
- MODULE, "cmis-media", remote -> 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 | 407 ++++++++++++++++++++++++++++++++++++
net/ethtool/loopback.c | 6 +-
net/ethtool/netlink.h | 8 +
4 files changed, 421 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..c4e8808ceaff
--- /dev/null
+++ b/net/ethtool/cmis_loopback.c
@@ -0,0 +1,407 @@
+// 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", remote
+ * Byte 181 (0xb5): Media Side Input -> MODULE, "cmis-media", local
+ * Byte 182 (0xb6): Host Side Output -> MODULE, "cmis-host", remote
+ * Byte 183 (0xb7): Host Side Input -> MODULE, "cmis-host", local
+ */
+#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);
+}
+
+/**
+ * cmis_loopback_read - Read CMIS loopback capabilities and build entries
+ * @dev: Network device with get_module_eeprom_by_page support
+ * @host: Output host loopback entry (populated if host caps exist)
+ * @media: Output media loopback entry (populated if media caps exist)
+ * @has_host: Set to true if host loopback is supported
+ * @has_media: Set to true if media loopback is supported
+ *
+ * Common helper that reads CMIS caps and control bytes, then populates
+ * the host and media entries with current state.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if no CMIS loopback support,
+ * negative errno on failure.
+ */
+static int cmis_loopback_read(struct net_device *dev,
+ struct ethtool_loopback_entry *host,
+ struct ethtool_loopback_entry *media,
+ bool *has_host, bool *has_media)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_module_eeprom page = {};
+ u8 ctrl[CMIS_LB_CTRL_LEN];
+ int caps, ret;
+
+ *has_host = false;
+ *has_media = false;
+
+ if (dev->ethtool->module_fw_flash_in_progress)
+ return -EBUSY;
+
+ caps = cmis_loopback_caps(dev);
+ if (caps <= 0)
+ return caps ? caps : -EOPNOTSUPP;
+
+ 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;
+
+ memset(host, 0, sizeof(*host));
+ host->component = ETHTOOL_LOOPBACK_COMPONENT_MODULE;
+ strscpy(host->name, CMIS_LB_NAME_HOST, sizeof(host->name));
+
+ memset(media, 0, sizeof(*media));
+ media->component = ETHTOOL_LOOPBACK_COMPONENT_MODULE;
+ strscpy(media->name, CMIS_LB_NAME_MEDIA, sizeof(media->name));
+
+ if (caps & CMIS_LB_CAP_HOST_INPUT) {
+ *has_host = true;
+ host->supported |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_INPUT])
+ host->direction |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL;
+ }
+ if (caps & CMIS_LB_CAP_HOST_OUTPUT) {
+ *has_host = true;
+ host->supported |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_OUTPUT])
+ host->direction |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_INPUT) {
+ *has_media = true;
+ media->supported |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_INPUT])
+ media->direction |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_OUTPUT) {
+ *has_media = true;
+ media->supported |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_OUTPUT])
+ media->direction |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
+ }
+
+ return 0;
+}
+
+/**
+ * ethtool_cmis_get_loopback_by_index - Enumerate CMIS loopback entry by index
+ * @dev: Network device with get_module_eeprom_by_page support
+ * @index: Zero-based index of the loopback entry to retrieve
+ * @entry: Output loopback entry
+ *
+ * Used by the dump infrastructure to iterate one entry at a time.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the index is out of range or
+ * no CMIS loopback support, negative errno on failure.
+ */
+int ethtool_cmis_get_loopback_by_index(struct net_device *dev, u32 index,
+ struct ethtool_loopback_entry *entry)
+{
+ struct ethtool_loopback_entry host, media;
+ bool has_host, has_media;
+ u32 cur = 0;
+ int ret;
+
+ ret = cmis_loopback_read(dev, &host, &media, &has_host, &has_media);
+ if (ret)
+ return ret;
+
+ if (has_host) {
+ if (cur == index) {
+ memcpy(entry, &host, sizeof(*entry));
+ return 0;
+ }
+ cur++;
+ }
+
+ if (has_media) {
+ if (cur == index) {
+ memcpy(entry, &media, sizeof(*entry));
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * ethtool_cmis_get_loopback - Look up CMIS loopback entry by name
+ * @dev: Network device with get_module_eeprom_by_page support
+ * @name: Loopback point name ("cmis-host" or "cmis-media")
+ * @entry: Output loopback entry
+ *
+ * Used by doit requests to look up a specific loopback point.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if name doesn't match or no CMIS
+ * support, negative errno on failure.
+ */
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ const char *name,
+ struct ethtool_loopback_entry *entry)
+{
+ struct ethtool_loopback_entry host, media;
+ bool has_host, has_media;
+ int ret;
+
+ ret = cmis_loopback_read(dev, &host, &media, &has_host, &has_media);
+ if (ret)
+ return ret;
+
+ if (has_host && !strcmp(name, CMIS_LB_NAME_HOST)) {
+ memcpy(entry, &host, sizeof(*entry));
+ return 0;
+ }
+
+ if (has_media && !strcmp(name, CMIS_LB_NAME_MEDIA)) {
+ memcpy(entry, &media, sizeof(*entry));
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * ethtool_cmis_set_loopback - 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(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 local_idx, remote_idx;
+ u8 local_cap, remote_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)) {
+ local_idx = CMIS_LB_CTRL_IDX_HOST_INPUT;
+ remote_idx = CMIS_LB_CTRL_IDX_HOST_OUTPUT;
+ local_cap = CMIS_LB_CAP_HOST_INPUT;
+ remote_cap = CMIS_LB_CAP_HOST_OUTPUT;
+ } else if (!strcmp(entry->name, CMIS_LB_NAME_MEDIA)) {
+ local_idx = CMIS_LB_CTRL_IDX_MEDIA_INPUT;
+ remote_idx = CMIS_LB_CTRL_IDX_MEDIA_OUTPUT;
+ local_cap = CMIS_LB_CAP_MEDIA_INPUT;
+ remote_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[local_idx]) {
+ ctrl[local_idx] = 0x00;
+ mod = true;
+ }
+ if (ctrl[remote_idx]) {
+ ctrl[remote_idx] = 0x00;
+ mod = true;
+ }
+ } else {
+ int enable_idx, disable_idx;
+ u8 enable_cap;
+
+ if (entry->direction & ETHTOOL_LOOPBACK_DIRECTION_LOCAL) {
+ enable_idx = local_idx;
+ enable_cap = local_cap;
+ disable_idx = remote_idx;
+ } else {
+ enable_idx = remote_idx;
+ enable_cap = remote_cap;
+ disable_idx = local_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 03b86662dc0f..2d0ad62ce42f 100644
--- a/net/ethtool/loopback.c
+++ b/net/ethtool/loopback.c
@@ -88,6 +88,8 @@ static int loopback_get(struct net_device *dev,
struct ethtool_loopback_entry *entry)
{
switch (component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
+ return ethtool_cmis_get_loopback(dev, name, entry);
default:
return -EOPNOTSUPP;
}
@@ -96,7 +98,7 @@ static int loopback_get(struct net_device *dev,
static int loopback_get_by_index(struct net_device *dev, u32 index,
struct ethtool_loopback_entry *entry)
{
- return -EOPNOTSUPP;
+ return ethtool_cmis_get_loopback_by_index(dev, index, entry);
}
static int loopback_prepare_data(const struct ethnl_req_info *req_base,
@@ -233,6 +235,8 @@ static int __loopback_set(struct net_device *dev,
struct netlink_ext_ack *extack)
{
switch (entry->component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
+ return ethtool_cmis_set_loopback(dev, entry, extack);
default:
return -EOPNOTSUPP;
}
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 693df12eef63..1017d653673d 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -556,6 +556,14 @@ int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
struct ethnl_dump_ctx *ctx,
unsigned long *pos_sub,
const struct genl_info *info);
+int ethtool_cmis_get_loopback_by_index(struct net_device *dev, u32 index,
+ struct ethtool_loopback_entry *entry);
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ const char *name,
+ struct ethtool_loopback_entry *entry);
+int ethtool_cmis_set_loopback(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
* [PATCH net-next v2 06/12] selftests: drv-net: Add loopback driver test
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (4 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 05/12] ethtool: Add CMIS loopback helpers for module loopback control Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops Björn Töpel
` (5 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
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 local and remote, 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>
---
.../testing/selftests/drivers/net/hw/Makefile | 1 +
.../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++++++++++
2 files changed, 228 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 3c97dac9baaa..5a6037a71f8f 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -32,6 +32,7 @@ TEST_PROGS = \
iou-zcrx.py \
irq.py \
loopback.sh \
+ loopback_drv.py \
nic_timestamp.py \
nk_netns.py \
pp_alloc_fail.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..2d4652386159
--- /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_LOCAL = {'local'}
+DIR_REMOTE = {'remote'}
+
+
+def _get_loopback(cfg):
+ """GET loopback and return the list of entries (via DUMP)."""
+ results = cfg.ethnl.loopback_get({
+ 'header': {'dev-index': cfg.ifindex}
+ }, dump=True)
+ entries = []
+ for msg in results:
+ if 'entry' in msg:
+ entries.extend(msg['entry'])
+ return entries
+
+
+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."""
+ entries = _get_loopback(cfg)
+ 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_local(cfg):
+ """SET a module entry to local loopback and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'local' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No local 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'], 'local')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'local' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_LOCAL)
+
+
+@ksft_disruptive
+def test_set_remote(cfg):
+ """SET a module entry to remote loopback and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ far = [e for e in mod_entries
+ if 'remote' in e['supported']]
+ if not far:
+ raise KsftSkipEx("No remote 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'], 'remote')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'remote' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_REMOTE)
+
+
+@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 'local' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No local 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'], 'local')
+ 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 local, then switch to remote. The kernel must disable
+ local before enabling remote (mutual exclusivity).
+ """
+ mod_entries = _require_module_entries(cfg)
+
+ both = [e for e in mod_entries
+ if 'local' in e['supported'] and 'remote' in e['supported']]
+ if not both:
+ raise KsftSkipEx("No entry with both local and remote 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'], 'local')
+ 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_LOCAL)
+
+ # Switch to remote
+ _set_loopback(cfg, 'module', target['name'], 'remote')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_REMOTE,
+ "Should have switched to remote")
+
+
+@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 'local' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No local 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'], 'local')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ # Second enable of the same direction should succeed
+ _set_loopback(cfg, 'module', target['name'], 'local')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'local' in e['supported']]
+ ksft_eq(updated[0]['direction'], DIR_LOCAL,
+ "Direction should still be local")
+
+
+@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 = 'local'
+ if direction not in target['supported']:
+ direction = 'remote'
+
+ 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:
+ """Run loopback driver tests."""
+ with NetDrvEnv(__file__, nsim_test=False) as cfg:
+ cfg.ethnl = EthtoolFamily()
+
+ ksft_run([
+ test_set_local,
+ test_set_remote,
+ 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
* [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (5 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 06/12] selftests: drv-net: Add loopback driver test Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-26 9:49 ` Breno Leitao
2026-03-25 14:50 ` [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation Björn Töpel
` (4 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Extend struct ethtool_ops with three loopback callbacks that drivers
can implement for MAC-level loopback control:
- get_loopback(dev, name, id, entry): exact lookup by name and
instance id, used by doit (single-entry GET) requests.
- get_loopback_by_index(dev, index, entry): flat enumeration by
index, used by dumpit (multi-entry GET) requests to iterate all
loopback points on a device.
- set_loopback(dev, entry, extack): apply a loopback configuration
change. Returns 1 if hardware state changed, 0 if no-op.
Wire the MAC component into loopback.c's dispatch functions. For dump
enumeration, MAC entries are tried first via the driver's
get_loopback_by_index() op, then MODULE/CMIS entries follow at the
next index offset.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
include/linux/ethtool.h | 10 ++++++++
net/ethtool/loopback.c | 56 ++++++++++++++++++++++++++++++++---------
2 files changed, 54 insertions(+), 12 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 81a9c186564d..971be759a915 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -1168,6 +1168,9 @@ struct kernel_ethtool_ts_info {
* @get_mm: Query the 802.3 MAC Merge layer state.
* @set_mm: Set the 802.3 MAC Merge layer parameters.
* @get_mm_stats: Query the 802.3 MAC Merge layer statistics.
+ * @get_loopback: Get the state of a loopback entry identified by name and id.
+ * @get_loopback_by_index: Get the state of a loopback entry by its index.
+ * @set_loopback: Set the loopback mode for a given entry.
*
* All operations are optional (i.e. the function pointer may be set
* to %NULL) and callers must take this into account. Callers must
@@ -1337,6 +1340,13 @@ struct ethtool_ops {
int (*set_mm)(struct net_device *dev, struct ethtool_mm_cfg *cfg,
struct netlink_ext_ack *extack);
void (*get_mm_stats)(struct net_device *dev, struct ethtool_mm_stats *stats);
+ int (*get_loopback)(struct net_device *dev, const char *name,
+ u32 id, struct ethtool_loopback_entry *entry);
+ int (*get_loopback_by_index)(struct net_device *dev, u32 index,
+ struct ethtool_loopback_entry *entry);
+ int (*set_loopback)(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack);
};
int ethtool_check_ops(const struct ethtool_ops *ops);
diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c
index 2d0ad62ce42f..60eb7b94a716 100644
--- a/net/ethtool/loopback.c
+++ b/net/ethtool/loopback.c
@@ -88,6 +88,10 @@ static int loopback_get(struct net_device *dev,
struct ethtool_loopback_entry *entry)
{
switch (component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MAC:
+ if (!dev->ethtool_ops->get_loopback)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->get_loopback(dev, name, id, entry);
case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
return ethtool_cmis_get_loopback(dev, name, entry);
default:
@@ -95,10 +99,22 @@ static int loopback_get(struct net_device *dev,
}
}
-static int loopback_get_by_index(struct net_device *dev, u32 index,
+static int loopback_get_by_index(struct net_device *dev,
+ enum ethtool_loopback_component component,
+ u32 index,
struct ethtool_loopback_entry *entry)
{
- return ethtool_cmis_get_loopback_by_index(dev, index, entry);
+ switch (component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MAC:
+ if (!dev->ethtool_ops->get_loopback_by_index)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->get_loopback_by_index(dev, index,
+ entry);
+ case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
+ return ethtool_cmis_get_loopback_by_index(dev, index, entry);
+ default:
+ return -EOPNOTSUPP;
+ }
}
static int loopback_prepare_data(const struct ethnl_req_info *req_base,
@@ -118,7 +134,8 @@ static int loopback_prepare_data(const struct ethnl_req_info *req_base,
ret = loopback_get(dev, req_info->component, req_info->id,
req_info->name, &data->entry);
else
- ret = loopback_get_by_index(dev, req_info->index, &data->entry);
+ ret = loopback_get_by_index(dev, req_info->component,
+ req_info->index, &data->entry);
ethnl_ops_complete(dev);
@@ -235,6 +252,10 @@ static int __loopback_set(struct net_device *dev,
struct netlink_ext_ack *extack)
{
switch (entry->component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MAC:
+ if (!dev->ethtool_ops->set_loopback)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->set_loopback(dev, entry, extack);
case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
return ethtool_cmis_set_loopback(dev, entry, extack);
default:
@@ -284,20 +305,31 @@ static int loopback_dump_one_dev(struct sk_buff *skb,
{
struct loopback_req_info *req_info =
container_of(ctx->req_info, struct loopback_req_info, base);
+ /* pos_sub encodes: upper 16 bits = component phase, lower 16 = index
+ * within that component. dump_one_dev is called repeatedly with
+ * increasing pos_sub until all components are exhausted.
+ */
+ enum ethtool_loopback_component phase = *pos_sub >> 16;
+ u32 idx = *pos_sub & 0xffff;
int ret;
- for (;; (*pos_sub)++) {
- req_info->index = *pos_sub;
- ret = ethnl_default_dump_one(skb, ctx->req_info->dev, ctx,
- info);
- if (ret == -EOPNOTSUPP)
- break;
- if (ret)
- return ret;
+ for (; phase <= ETHTOOL_LOOPBACK_COMPONENT_MODULE; phase++) {
+ for (;; idx++) {
+ req_info->component = phase;
+ req_info->index = idx;
+ ret = ethnl_default_dump_one(skb, ctx->req_info->dev,
+ ctx, info);
+ if (ret == -EOPNOTSUPP)
+ break;
+ if (ret) {
+ *pos_sub = ((unsigned long)phase << 16) | idx;
+ return ret;
+ }
+ }
+ idx = 0;
}
*pos_sub = 0;
-
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (6 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-26 9:40 ` Breno Leitao
2026-03-25 14:50 ` [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test Björn Töpel
` (3 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Implement the three ethtool loopback ops for MAC-level loopback
simulation:
- get_loopback(): exact lookup by name ("mac")
- get_loopback_by()_index: enumerate (single entry at index 0)
- set_loopback(): update direction, return 1 if changed
The MAC loopback entry announces support for both local and remote
support by default. State is stored in nsim_ethtool.mac_lb and exposed
via debugfs under ethtool/mac_lb/{supported,direction} for test
inspection and control.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
drivers/net/netdevsim/ethtool.c | 64 +++++++++++++++++++++++++++++++
drivers/net/netdevsim/netdevsim.h | 4 ++
2 files changed, 68 insertions(+)
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 36a201533aae..39c681284700 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -195,6 +195,58 @@ nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats,
values[2].per_lane[3] = 0;
}
+static void nsim_fill_mac_lb_entry(struct netdevsim *ns,
+ struct ethtool_loopback_entry *entry)
+{
+ memset(entry, 0, sizeof(*entry));
+ entry->component = ETHTOOL_LOOPBACK_COMPONENT_MAC;
+ strscpy(entry->name, "mac", sizeof(entry->name));
+ entry->supported = ns->ethtool.mac_lb.supported;
+ entry->direction = ns->ethtool.mac_lb.direction;
+}
+
+static int nsim_get_loopback(struct net_device *dev, const char *name,
+ u32 id, struct ethtool_loopback_entry *entry)
+{
+ struct netdevsim *ns = netdev_priv(dev);
+
+ if (strcmp(name, "mac"))
+ return -EOPNOTSUPP;
+
+ nsim_fill_mac_lb_entry(ns, entry);
+ return 0;
+}
+
+static int nsim_get_loopback_by_index(struct net_device *dev, u32 index,
+ struct ethtool_loopback_entry *entry)
+{
+ struct netdevsim *ns = netdev_priv(dev);
+
+ if (index > 0)
+ return -EOPNOTSUPP;
+
+ nsim_fill_mac_lb_entry(ns, entry);
+ return 0;
+}
+
+static int nsim_set_loopback(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ struct netdevsim *ns = netdev_priv(dev);
+
+ if (strcmp(entry->name, "mac")) {
+ NL_SET_ERR_MSG(extack, "Unknown MAC loopback name");
+ return -EOPNOTSUPP;
+ }
+
+ if (ns->ethtool.mac_lb.direction == entry->direction)
+ return 0;
+
+ ns->ethtool.mac_lb.direction = entry->direction;
+ return 1;
+}
+
static int nsim_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info)
{
@@ -222,6 +274,9 @@ 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_loopback = nsim_get_loopback,
+ .get_loopback_by_index = nsim_get_loopback_by_index,
+ .set_loopback = nsim_set_loopback,
};
static void nsim_ethtool_ring_init(struct netdevsim *ns)
@@ -270,4 +325,13 @@ 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);
+
+ ns->ethtool.mac_lb.supported = ETHTOOL_LOOPBACK_DIRECTION_LOCAL |
+ ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
+
+ dir = debugfs_create_dir("mac_lb", ethtool);
+ debugfs_create_u32("supported", 0600, dir,
+ &ns->ethtool.mac_lb.supported);
+ debugfs_create_u32("direction", 0600, dir,
+ &ns->ethtool.mac_lb.direction);
}
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index c904e14f6b3f..7a3382562287 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -90,6 +90,10 @@ struct nsim_ethtool {
struct ethtool_coalesce coalesce;
struct ethtool_ringparam ring;
struct ethtool_fecparam fec;
+ struct {
+ u32 supported;
+ u32 direction;
+ } mac_lb;
};
struct nsim_rq {
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (7 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-26 9:32 ` Breno Leitao
2026-03-25 14:50 ` [PATCH net-next v2 10/12] MAINTAINERS: Add entry for ethtool loopback Björn Töpel
` (2 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Add loopback_nsim.py with netdevsim-specific tests for MAC loopback
via the new ethtool_ops loopback callbacks:
- test_get_mac_entry: verify MAC entry appears in dump with correct
component, name, and supported directions
- test_set_mac_local: SET local, verify via GET and debugfs
- test_set_mac_disable: enable then disable
- test_set_mac_unknown_name: SET with wrong name, expect EOPNOTSUPP
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../testing/selftests/drivers/net/hw/Makefile | 1 +
.../selftests/drivers/net/hw/loopback_nsim.py | 138 ++++++++++++++++++
2 files changed, 139 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_nsim.py
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 5a6037a71f8f..74b0e1937980 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -33,6 +33,7 @@ TEST_PROGS = \
irq.py \
loopback.sh \
loopback_drv.py \
+ loopback_nsim.py \
nic_timestamp.py \
nk_netns.py \
pp_alloc_fail.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..d05be09f6c14
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Netdevsim-specific tests for MAC loopback via ethtool_ops.
+
+Verifies that MAC loopback entries appear in dumps, that SET
+operations update state correctly (both via GET and debugfs).
+"""
+
+import errno
+import os
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import KsftFailEx, ksft_disruptive
+from lib.py import EthtoolFamily, NlError
+from lib.py import NetDrvEnv, ip, defer
+
+# Direction flags as YNL returns them
+DIR_NONE = set()
+DIR_LOCAL = {'local'}
+DIR_REMOTE = {'remote'}
+
+
+def _nsim_dfs_path(cfg):
+ return cfg._ns.nsims[0].dfs_dir # pylint: disable=protected-access
+
+
+def _dfs_read_u32(cfg, path):
+ with open(os.path.join(_nsim_dfs_path(cfg), path),
+ encoding="utf-8") as f:
+ return int(f.read().strip())
+
+
+def _dfs_write_u32(cfg, path, val):
+ with open(os.path.join(_nsim_dfs_path(cfg), path), "w",
+ encoding="utf-8") as f:
+ f.write(str(val))
+
+
+def _get_loopback(cfg):
+ results = cfg.ethnl.loopback_get({
+ 'header': {'dev-index': cfg.ifindex}
+ }, dump=True)
+ entries = []
+ for msg in results:
+ if 'entry' in msg:
+ entries.extend(msg['entry'])
+ return entries
+
+
+def _set_loopback(cfg, component, name, direction):
+ cfg.ethnl.loopback_set({
+ 'header': {'dev-index': cfg.ifindex},
+ 'entry': [{
+ 'component': component,
+ 'name': name,
+ 'direction': direction,
+ }]
+ })
+
+
+def test_get_mac_entry(cfg):
+ """GET should return the MAC loopback entry with correct attributes."""
+ entries = _get_loopback(cfg)
+ mac_entries = [e for e in entries if e['component'] == 'mac']
+
+ ksft_eq(len(mac_entries), 1, "Expected 1 MAC loopback entry")
+ ksft_eq(mac_entries[0]['name'], 'mac')
+ ksft_eq(mac_entries[0]['supported'], DIR_LOCAL | DIR_REMOTE)
+ ksft_eq(mac_entries[0]['direction'], DIR_NONE)
+
+
+@ksft_disruptive
+def test_set_mac_local(cfg):
+ """SET MAC local loopback and verify via GET and debugfs."""
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ _set_loopback(cfg, 'mac', 'mac', 'local')
+ defer(_set_loopback, cfg, 'mac', 'mac', 0)
+
+ entries = _get_loopback(cfg)
+ mac = [e for e in entries if e['component'] == 'mac']
+ ksft_eq(mac[0]['direction'], DIR_LOCAL)
+
+ dfs_dir = _dfs_read_u32(cfg, "ethtool/mac_lb/direction")
+ ksft_eq(dfs_dir, 1, "debugfs direction should be 1 (LOCAL)")
+
+
+@ksft_disruptive
+def test_set_mac_disable(cfg):
+ """Enable then disable MAC loopback."""
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ _set_loopback(cfg, 'mac', 'mac', 'local')
+ defer(_set_loopback, cfg, 'mac', 'mac', 0)
+
+ _set_loopback(cfg, 'mac', 'mac', 0)
+
+ entries = _get_loopback(cfg)
+ mac = [e for e in entries if e['component'] == 'mac']
+ ksft_eq(mac[0]['direction'], DIR_NONE, "Direction should be off")
+
+ dfs_dir = _dfs_read_u32(cfg, "ethtool/mac_lb/direction")
+ ksft_eq(dfs_dir, 0, "debugfs direction should be 0")
+
+
+@ksft_disruptive
+def test_set_mac_unknown_name(cfg):
+ """SET with unknown name should fail with EOPNOTSUPP."""
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ try:
+ _set_loopback(cfg, 'mac', 'bogus', 'local')
+ raise KsftFailEx("Should have rejected unknown name")
+ except NlError as e:
+ ksft_eq(e.error, errno.EOPNOTSUPP,
+ "Expected EOPNOTSUPP for unknown name")
+
+
+def main() -> None:
+ """Run netdevsim loopback tests."""
+ with NetDrvEnv(__file__) as cfg:
+ cfg.ethnl = EthtoolFamily()
+
+ ksft_run([
+ test_get_mac_entry,
+ test_set_mac_local,
+ test_set_mac_disable,
+ test_set_mac_unknown_name,
+ ], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 10/12] MAINTAINERS: Add entry for ethtool loopback
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (8 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 11/12] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 12/12] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Add a MAINTAINERS entry for the ethtool loopback subsystem covering
the core loopback and CMIS loopback netlink implementation, and the
associated selftests.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index a09bf30a057d..411c412975ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18389,6 +18389,12 @@ F: net/ethtool/cabletest.c
F: tools/testing/selftests/drivers/net/*/ethtool*
K: cable_test
+NETWORKING [ETHTOOL LOOPBACK]
+M: Björn Töpel <bjorn@kernel.org>
+F: net/ethtool/cmis_loopback.c
+F: net/ethtool/loopback.c
+F: tools/testing/selftests/drivers/net/hw/loopback*
+
NETWORKING [ETHTOOL MAC MERGE]
M: Vladimir Oltean <vladimir.oltean@nxp.com>
F: net/ethtool/mm.c
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 11/12] netdevsim: Add module EEPROM simulation via debugfs
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (9 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 10/12] MAINTAINERS: Add entry for ethtool loopback Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 12/12] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
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 | 83 +++++++++++++++++++++++++++++++
drivers/net/netdevsim/netdevsim.h | 11 ++++
2 files changed, 94 insertions(+)
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 39c681284700..1ef48802fcbd 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -247,6 +247,68 @@ static int nsim_set_loopback(struct net_device *dev,
return 1;
}
+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;
+ }
+
+ *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)
{
@@ -274,6 +336,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,
.get_loopback = nsim_get_loopback,
.get_loopback_by_index = nsim_get_loopback_by_index,
.set_loopback = nsim_set_loopback,
@@ -292,6 +356,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;
@@ -326,6 +391,24 @@ void nsim_ethtool_init(struct netdevsim *ns)
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]);
+ }
+
ns->ethtool.mac_lb.supported = ETHTOOL_LOOPBACK_DIRECTION_LOCAL |
ETHTOOL_LOOPBACK_DIRECTION_REMOTE;
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index 7a3382562287..816e8ddd2551 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;
@@ -94,6 +104,7 @@ struct nsim_ethtool {
u32 supported;
u32 direction;
} mac_lb;
+ struct nsim_ethtool_module module;
};
struct nsim_rq {
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH net-next v2 12/12] selftests: drv-net: Add CMIS loopback netdevsim test
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
` (10 preceding siblings ...)
2026-03-25 14:50 ` [PATCH net-next v2 11/12] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
@ 2026-03-25 14:50 ` Björn Töpel
11 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-25 14:50 UTC (permalink / raw)
To: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Björn Töpel, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Extend 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, rejection without CMIS support,
and combined MAC + MODULE dump.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../selftests/drivers/net/hw/loopback_nsim.py | 207 +++++++++++++++++-
1 file changed, 206 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
index d05be09f6c14..f824f5f5d791 100755
--- a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
+++ b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py
@@ -1,10 +1,13 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-"""Netdevsim-specific tests for MAC loopback via ethtool_ops.
+"""Netdevsim-specific tests for ethtool loopback.
Verifies that MAC loopback entries appear in dumps, that SET
operations update state correctly (both via GET and debugfs).
+
+Seeds the CMIS EEPROM via debugfs and verifies register-level
+behavior that can only be checked with controlled EEPROM contents.
"""
import errno
@@ -15,6 +18,20 @@ from lib.py import 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
DIR_NONE = set()
DIR_LOCAL = {'local'}
@@ -37,6 +54,54 @@ def _dfs_write_u32(cfg, path, val):
f.write(str(val))
+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):
results = cfg.ethnl.loopback_get({
'header': {'dev-index': cfg.ifindex}
@@ -120,6 +185,138 @@ def test_set_mac_unknown_name(cfg):
"Expected EOPNOTSUPP for unknown name")
+def test_get_no_module(cfg):
+ """GET on a device with no CMIS module returns no module entries."""
+ _nsim_clear_cmis(cfg)
+
+ entries = _get_loopback(cfg)
+ mod_entries = [e for e in entries if e['component'] == 'module']
+ ksft_eq(len(mod_entries), 0, "Expected no module entries 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_LOCAL | DIR_REMOTE)
+ ksft_eq(media[0]['supported'], DIR_LOCAL | DIR_REMOTE)
+
+ 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_LOCAL)
+
+
+@ksft_disruptive
+def test_set_verify_eeprom(cfg):
+ """SET local loopback 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', 'local')
+ 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', 'local')
+ defer(_set_loopback, cfg, 'module', 'cmis-host', 0)
+
+ # Switch to remote
+ _set_loopback(cfg, 'module', 'cmis-host', 'remote')
+
+ # Local (Host Input, Byte 183) should be disabled
+ val = _nsim_read_page_byte(cfg, 0x13, 183)
+ ksft_eq(val, 0x00, "Local should be disabled after switch")
+ # Remote (Host Output, Byte 182) should be enabled
+ val = _nsim_read_page_byte(cfg, 0x13, 182)
+ ksft_eq(val, 0xff, "Remote 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 local
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ try:
+ _set_loopback(cfg, 'module', 'cmis-host', 'remote')
+ 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', 'local')
+ 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 test_combined_dump(cfg):
+ """Dump should return both MAC and MODULE entries."""
+ _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL)
+ defer(_nsim_clear_cmis, cfg)
+
+ entries = _get_loopback(cfg)
+ mac_entries = [e for e in entries if e['component'] == 'mac']
+ mod_entries = [e for e in entries if e['component'] == 'module']
+
+ ksft_eq(len(mac_entries), 1, "Expected 1 MAC entry")
+ ksft_eq(len(mod_entries), 2, "Expected 2 MODULE entries")
+ ksft_eq(mac_entries[0]['name'], 'mac')
+
+
def main() -> None:
"""Run netdevsim loopback tests."""
with NetDrvEnv(__file__) as cfg:
@@ -130,6 +327,14 @@ def main() -> None:
test_set_mac_local,
test_set_mac_disable,
test_set_mac_unknown_name,
+ 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,
+ test_combined_dump,
], args=(cfg, ))
ksft_exit()
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration
2026-03-25 14:50 ` [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration Björn Töpel
@ 2026-03-25 18:20 ` Maxime Chevallier
0 siblings, 0 replies; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-25 18:20 UTC (permalink / raw)
To: Björn Töpel, netdev, David S. Miller, Andrew Lunn,
Donald Hunter, Eric Dumazet, Jakub Kicinski, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Danielle Ratson, Hariprasad Kelam, Ido Schimmel, Kory Maincent,
Leon Romanovsky, Michael Chan, Oleksij Rempel, Pavan Chebbi,
Piergiorgio Beruto, Russell King, Saeed Mahameed, Shuah Khan,
Tariq Toukan, Willem de Bruijn, Kees Cook, linux-kernel,
linux-kselftest, linux-rdma
Hi Björn,
On 25/03/2026 15:50, Björn Töpel wrote:
> Add the dump_one_dev callback to ethnl_request_ops, allowing commands
> to provide custom per-device dump logic with sub-positioning. Extend
> ethnl_dump_ctx with ifindex and pos_sub fields.
>
> No functional change; no command uses dump_one_dev yet.
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
Awesome :) I confirm that the phy dump still works just fine, and this
looks much simpler to me.
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Tested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Maxime
> ---
> net/ethtool/netlink.c | 66 ++++++++++++++++++-------------------------
> net/ethtool/netlink.h | 31 ++++++++++++++++++++
> 2 files changed, 58 insertions(+), 39 deletions(-)
>
> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
> index 5046023a30b1..8d161f0882d0 100644
> --- a/net/ethtool/netlink.c
> +++ b/net/ethtool/netlink.c
> @@ -346,36 +346,6 @@ int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
>
> /* GET request helpers */
>
> -/**
> - * struct ethnl_dump_ctx - context structure for generic dumpit() callback
> - * @ops: request ops of currently processed message type
> - * @req_info: parsed request header of processed request
> - * @reply_data: data needed to compose the reply
> - * @pos_ifindex: saved iteration position - ifindex
> - *
> - * These parameters are kept in struct netlink_callback as context preserved
> - * between iterations. They are initialized by ethnl_default_start() and used
> - * in ethnl_default_dumpit() and ethnl_default_done().
> - */
> -struct ethnl_dump_ctx {
> - const struct ethnl_request_ops *ops;
> - struct ethnl_req_info *req_info;
> - struct ethnl_reply_data *reply_data;
> - unsigned long pos_ifindex;
> -};
> -
> -/**
> - * struct ethnl_perphy_dump_ctx - context for dumpit() PHY-aware callbacks
> - * @ethnl_ctx: generic ethnl context
> - * @ifindex: For Filtered DUMP requests, the ifindex of the targeted netdev
> - * @pos_phyindex: iterator position for multi-msg DUMP
> - */
> -struct ethnl_perphy_dump_ctx {
> - struct ethnl_dump_ctx ethnl_ctx;
> - unsigned int ifindex;
> - unsigned long pos_phyindex;
> -};
> -
> static const struct ethnl_request_ops *
> ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
> [ETHTOOL_MSG_STRSET_GET] = ðnl_strset_request_ops,
> @@ -618,6 +588,7 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
> struct netlink_callback *cb)
> {
> struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
> + const struct genl_info *info = genl_info_dump(cb);
> struct net *net = sock_net(skb->sk);
> netdevice_tracker dev_tracker;
> struct net_device *dev;
> @@ -625,10 +596,20 @@ static int ethnl_default_dumpit(struct sk_buff *skb,
>
> rcu_read_lock();
> for_each_netdev_dump(net, dev, ctx->pos_ifindex) {
> + if (ctx->ifindex && ctx->ifindex != ctx->pos_ifindex)
> + break;
> +
> netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
> rcu_read_unlock();
>
> - ret = ethnl_default_dump_one(skb, dev, ctx, genl_info_dump(cb));
> + if (ctx->ops->dump_one_dev) {
> + ctx->req_info->dev = dev;
> + ret = ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub,
> + info);
> + ctx->req_info->dev = NULL;
> + } else {
> + ret = ethnl_default_dump_one(skb, dev, ctx, info);
> + }
>
> rcu_read_lock();
> netdev_put(dev, &dev_tracker);
> @@ -674,19 +655,26 @@ static int ethnl_default_start(struct netlink_callback *cb)
> ret = ethnl_default_parse(req_info, &info->info, ops, false);
> if (ret < 0)
> goto free_reply_data;
> - if (req_info->dev) {
> - /* We ignore device specification in dump requests but as the
> - * same parser as for non-dump (doit) requests is used, it
> - * would take reference to the device if it finds one
> - */
> - netdev_put(req_info->dev, &req_info->dev_tracker);
> - req_info->dev = NULL;
> - }
>
> ctx->ops = ops;
> ctx->req_info = req_info;
> ctx->reply_data = reply_data;
> ctx->pos_ifindex = 0;
> + ctx->ifindex = 0;
> + ctx->pos_sub = 0;
> +
> + if (req_info->dev) {
> + if (ops->dump_one_dev) {
> + /* Sub-iterator dumps keep track of the dev's ifindex
> + * so the dumpit handler can grab/release the netdev
> + * per iteration.
> + */
> + ctx->ifindex = req_info->dev->ifindex;
> + ctx->pos_ifindex = ctx->ifindex;
> + }
> + netdev_put(req_info->dev, &req_info->dev_tracker);
> + req_info->dev = NULL;
> + }
>
> return 0;
>
> diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
> index aaf6f2468768..e01adc5db02f 100644
> --- a/net/ethtool/netlink.h
> +++ b/net/ethtool/netlink.h
> @@ -10,6 +10,28 @@
>
> struct ethnl_req_info;
>
> +/**
> + * struct ethnl_dump_ctx - context structure for generic dumpit() callback
> + * @ops: request ops of currently processed message type
> + * @req_info: parsed request header of processed request
> + * @reply_data: data needed to compose the reply
> + * @pos_ifindex: saved iteration position - ifindex
> + * @ifindex: for filtered dump requests, the ifindex of the targeted netdev
> + * @pos_sub: iterator position for per-device iteration
> + *
> + * These parameters are kept in struct netlink_callback as context preserved
> + * between iterations. They are initialized by ethnl_default_start() and used
> + * in ethnl_default_dumpit() and ethnl_default_done().
> + */
> +struct ethnl_dump_ctx {
> + const struct ethnl_request_ops *ops;
> + struct ethnl_req_info *req_info;
> + struct ethnl_reply_data *reply_data;
> + unsigned long pos_ifindex;
> + unsigned int ifindex;
> + unsigned long pos_sub;
> +};
> +
> u32 ethnl_bcast_seq_next(void);
> int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
> const struct nlattr *nest, struct net *net,
> @@ -365,6 +387,10 @@ int ethnl_sock_priv_set(struct sk_buff *skb, struct net_device *dev, u32 portid,
> * used e.g. to free any additional data structures outside the main
> * structure which were allocated by ->prepare_data(). When processing
> * dump requests, ->cleanup() is called for each message.
> + * @dump_one_dev:
> + * Optional callback for dumping data for a single device. When set,
> + * overrides the default dump behavior for GET requests, allowing
> + * per-device iteration with sub-positioning via @pos_sub.
> * @set_validate:
> * Check if set operation is supported for a given device, and perform
> * extra input checks. Expected return values:
> @@ -409,6 +435,11 @@ struct ethnl_request_ops {
> const struct ethnl_reply_data *reply_data);
> void (*cleanup_data)(struct ethnl_reply_data *reply_data);
>
> + int (*dump_one_dev)(struct sk_buff *skb,
> + struct ethnl_dump_ctx *ctx,
> + unsigned long *pos_sub,
> + const struct genl_info *info);
> +
> int (*set_validate)(struct ethnl_req_info *req_info,
> struct genl_info *info);
> int (*set)(struct ethnl_req_info *req_info,
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev
2026-03-25 14:50 ` [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev Björn Töpel
@ 2026-03-25 18:21 ` Maxime Chevallier
0 siblings, 0 replies; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-25 18:21 UTC (permalink / raw)
To: Björn Töpel, netdev, David S. Miller, Andrew Lunn,
Donald Hunter, Eric Dumazet, Jakub Kicinski, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Danielle Ratson, Hariprasad Kelam, Ido Schimmel, Kory Maincent,
Leon Romanovsky, Michael Chan, Oleksij Rempel, Pavan Chebbi,
Piergiorgio Beruto, Russell King, Saeed Mahameed, Shuah Khan,
Tariq Toukan, Willem de Bruijn, Kees Cook, linux-kernel,
linux-kselftest, linux-rdma
Hi Björn
On 25/03/2026 15:50, Björn Töpel wrote:
> Convert PSE, PLCA, PHY, and MSE commands from the separate
> ethnl_perphy_{start,dumpit,done} handlers to use the generic
> dump_one_dev callback. This removes the per-PHY specific dump
> infrastructure (ethnl_perphy_dump_ctx, ethnl_perphy_dump_context,
> ethnl_perphy_start, ethnl_perphy_dumpit, ethnl_perphy_done, and the
> internal helpers) in favor of a shared ethnl_perphy_dump_one_dev()
> function.
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Tested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Maxime
> ---
> net/ethtool/mse.c | 1 +
> net/ethtool/netlink.c | 194 ++++++------------------------------------
> net/ethtool/netlink.h | 4 +
> net/ethtool/phy.c | 1 +
> net/ethtool/plca.c | 2 +
> net/ethtool/pse-pd.c | 1 +
> 6 files changed, 35 insertions(+), 168 deletions(-)
>
> diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c
> index e91b74430f76..3f33182283ce 100644
> --- a/net/ethtool/mse.c
> +++ b/net/ethtool/mse.c
> @@ -325,4 +325,5 @@ const struct ethnl_request_ops ethnl_mse_request_ops = {
> .cleanup_data = mse_cleanup_data,
> .reply_size = mse_reply_size,
> .fill_reply = mse_fill_reply,
> + .dump_one_dev = ethnl_perphy_dump_one_dev,
> };
> diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
> index 8d161f0882d0..edeeca67918a 100644
> --- a/net/ethtool/netlink.c
> +++ b/net/ethtool/netlink.c
> @@ -399,12 +399,6 @@ static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
> return (struct ethnl_dump_ctx *)cb->ctx;
> }
>
> -static struct ethnl_perphy_dump_ctx *
> -ethnl_perphy_dump_context(struct netlink_callback *cb)
> -{
> - return (struct ethnl_perphy_dump_ctx *)cb->ctx;
> -}
> -
> /**
> * ethnl_default_parse() - Parse request message
> * @req_info: pointer to structure to put data into
> @@ -686,169 +680,33 @@ static int ethnl_default_start(struct netlink_callback *cb)
> return ret;
> }
>
> -/* per-PHY ->start() handler for GET requests */
> -static int ethnl_perphy_start(struct netlink_callback *cb)
> +/* Shared dump_one_dev for per-PHY commands (PSE, PLCA, PHY, MSE) */
> +int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
> + struct ethnl_dump_ctx *ctx,
> + unsigned long *pos_sub,
> + const struct genl_info *info)
> {
> - struct ethnl_perphy_dump_ctx *phy_ctx = ethnl_perphy_dump_context(cb);
> - const struct genl_dumpit_info *info = genl_dumpit_info(cb);
> - struct ethnl_dump_ctx *ctx = &phy_ctx->ethnl_ctx;
> - struct ethnl_reply_data *reply_data;
> - const struct ethnl_request_ops *ops;
> - struct ethnl_req_info *req_info;
> - struct genlmsghdr *ghdr;
> - int ret;
> -
> - BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
> -
> - ghdr = nlmsg_data(cb->nlh);
> - ops = ethnl_default_requests[ghdr->cmd];
> - if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd))
> - return -EOPNOTSUPP;
> - req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
> - if (!req_info)
> - return -ENOMEM;
> - reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
> - if (!reply_data) {
> - ret = -ENOMEM;
> - goto free_req_info;
> - }
> -
> - /* Unlike per-dev dump, don't ignore dev. The dump handler
> - * will notice it and dump PHYs from given dev. We only keep track of
> - * the dev's ifindex, .dumpit() will grab and release the netdev itself.
> - */
> - ret = ethnl_default_parse(req_info, &info->info, ops, false);
> - if (ret < 0)
> - goto free_reply_data;
> - if (req_info->dev) {
> - phy_ctx->ifindex = req_info->dev->ifindex;
> - netdev_put(req_info->dev, &req_info->dev_tracker);
> - req_info->dev = NULL;
> - }
> -
> - ctx->ops = ops;
> - ctx->req_info = req_info;
> - ctx->reply_data = reply_data;
> - ctx->pos_ifindex = 0;
> -
> - return 0;
> -
> -free_reply_data:
> - kfree(reply_data);
> -free_req_info:
> - kfree(req_info);
> -
> - return ret;
> -}
> -
> -static int ethnl_perphy_dump_one_dev(struct sk_buff *skb,
> - struct ethnl_perphy_dump_ctx *ctx,
> - const struct genl_info *info)
> -{
> - struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
> - struct net_device *dev = ethnl_ctx->req_info->dev;
> + struct net_device *dev = ctx->req_info->dev;
> struct phy_device_node *pdn;
> int ret;
>
> if (!dev->link_topo)
> return 0;
>
> - xa_for_each_start(&dev->link_topo->phys, ctx->pos_phyindex, pdn,
> - ctx->pos_phyindex) {
> - ethnl_ctx->req_info->phy_index = ctx->pos_phyindex;
> + xa_for_each_start(&dev->link_topo->phys, *pos_sub, pdn,
> + *pos_sub) {
> + ctx->req_info->phy_index = *pos_sub;
>
> /* We can re-use the original dump_one as ->prepare_data in
> * commands use ethnl_req_get_phydev(), which gets the PHY from
> * the req_info->phy_index
> */
> - ret = ethnl_default_dump_one(skb, dev, ethnl_ctx, info);
> + ret = ethnl_default_dump_one(skb, dev, ctx, info);
> if (ret)
> return ret;
> }
>
> - ctx->pos_phyindex = 0;
> -
> - return 0;
> -}
> -
> -static int ethnl_perphy_dump_all_dev(struct sk_buff *skb,
> - struct ethnl_perphy_dump_ctx *ctx,
> - const struct genl_info *info)
> -{
> - struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
> - struct net *net = sock_net(skb->sk);
> - netdevice_tracker dev_tracker;
> - struct net_device *dev;
> - int ret = 0;
> -
> - rcu_read_lock();
> - for_each_netdev_dump(net, dev, ethnl_ctx->pos_ifindex) {
> - netdev_hold(dev, &dev_tracker, GFP_ATOMIC);
> - rcu_read_unlock();
> -
> - /* per-PHY commands use ethnl_req_get_phydev(), which needs the
> - * net_device in the req_info
> - */
> - ethnl_ctx->req_info->dev = dev;
> - ret = ethnl_perphy_dump_one_dev(skb, ctx, info);
> -
> - rcu_read_lock();
> - netdev_put(dev, &dev_tracker);
> - ethnl_ctx->req_info->dev = NULL;
> -
> - if (ret < 0 && ret != -EOPNOTSUPP) {
> - if (likely(skb->len))
> - ret = skb->len;
> - break;
> - }
> - ret = 0;
> - }
> - rcu_read_unlock();
> -
> - return ret;
> -}
> -
> -/* per-PHY ->dumpit() handler for GET requests. */
> -static int ethnl_perphy_dumpit(struct sk_buff *skb,
> - struct netlink_callback *cb)
> -{
> - struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
> - const struct genl_dumpit_info *info = genl_dumpit_info(cb);
> - struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
> - int ret = 0;
> -
> - if (ctx->ifindex) {
> - netdevice_tracker dev_tracker;
> - struct net_device *dev;
> -
> - dev = netdev_get_by_index(genl_info_net(&info->info),
> - ctx->ifindex, &dev_tracker,
> - GFP_KERNEL);
> - if (!dev)
> - return -ENODEV;
> -
> - ethnl_ctx->req_info->dev = dev;
> - ret = ethnl_perphy_dump_one_dev(skb, ctx, genl_info_dump(cb));
> -
> - if (ret < 0 && ret != -EOPNOTSUPP && likely(skb->len))
> - ret = skb->len;
> -
> - netdev_put(dev, &dev_tracker);
> - } else {
> - ret = ethnl_perphy_dump_all_dev(skb, ctx, genl_info_dump(cb));
> - }
> -
> - return ret;
> -}
> -
> -/* per-PHY ->done() handler for GET requests */
> -static int ethnl_perphy_done(struct netlink_callback *cb)
> -{
> - struct ethnl_perphy_dump_ctx *ctx = ethnl_perphy_dump_context(cb);
> - struct ethnl_dump_ctx *ethnl_ctx = &ctx->ethnl_ctx;
> -
> - kfree(ethnl_ctx->reply_data);
> - kfree(ethnl_ctx->req_info);
> + *pos_sub = 0;
>
> return 0;
> }
> @@ -1410,9 +1268,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
> {
> .cmd = ETHTOOL_MSG_PSE_GET,
> .doit = ethnl_default_doit,
> - .start = ethnl_perphy_start,
> - .dumpit = ethnl_perphy_dumpit,
> - .done = ethnl_perphy_done,
> + .start = ethnl_default_start,
> + .dumpit = ethnl_default_dumpit,
> + .done = ethnl_default_done,
> .policy = ethnl_pse_get_policy,
> .maxattr = ARRAY_SIZE(ethnl_pse_get_policy) - 1,
> },
> @@ -1434,9 +1292,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
> {
> .cmd = ETHTOOL_MSG_PLCA_GET_CFG,
> .doit = ethnl_default_doit,
> - .start = ethnl_perphy_start,
> - .dumpit = ethnl_perphy_dumpit,
> - .done = ethnl_perphy_done,
> + .start = ethnl_default_start,
> + .dumpit = ethnl_default_dumpit,
> + .done = ethnl_default_done,
> .policy = ethnl_plca_get_cfg_policy,
> .maxattr = ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1,
> },
> @@ -1450,9 +1308,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
> {
> .cmd = ETHTOOL_MSG_PLCA_GET_STATUS,
> .doit = ethnl_default_doit,
> - .start = ethnl_perphy_start,
> - .dumpit = ethnl_perphy_dumpit,
> - .done = ethnl_perphy_done,
> + .start = ethnl_default_start,
> + .dumpit = ethnl_default_dumpit,
> + .done = ethnl_default_done,
> .policy = ethnl_plca_get_status_policy,
> .maxattr = ARRAY_SIZE(ethnl_plca_get_status_policy) - 1,
> },
> @@ -1482,9 +1340,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
> {
> .cmd = ETHTOOL_MSG_PHY_GET,
> .doit = ethnl_default_doit,
> - .start = ethnl_perphy_start,
> - .dumpit = ethnl_perphy_dumpit,
> - .done = ethnl_perphy_done,
> + .start = ethnl_default_start,
> + .dumpit = ethnl_default_dumpit,
> + .done = ethnl_default_done,
> .policy = ethnl_phy_get_policy,
> .maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1,
> },
> @@ -1528,9 +1386,9 @@ static const struct genl_ops ethtool_genl_ops[] = {
> {
> .cmd = ETHTOOL_MSG_MSE_GET,
> .doit = ethnl_default_doit,
> - .start = ethnl_perphy_start,
> - .dumpit = ethnl_perphy_dumpit,
> - .done = ethnl_perphy_done,
> + .start = ethnl_default_start,
> + .dumpit = ethnl_default_dumpit,
> + .done = ethnl_default_done,
> .policy = ethnl_mse_get_policy,
> .maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1,
> },
> diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
> index e01adc5db02f..dda2f5593ed9 100644
> --- a/net/ethtool/netlink.h
> +++ b/net/ethtool/netlink.h
> @@ -546,6 +546,10 @@ 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 ethnl_perphy_dump_one_dev(struct sk_buff *skb,
> + struct ethnl_dump_ctx *ctx,
> + unsigned long *pos_sub,
> + const struct genl_info *info);
>
> 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];
> diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c
> index d4e6887055ab..4bb23a5d6ad5 100644
> --- a/net/ethtool/phy.c
> +++ b/net/ethtool/phy.c
> @@ -162,4 +162,5 @@ const struct ethnl_request_ops ethnl_phy_request_ops = {
> .reply_size = phy_reply_size,
> .fill_reply = phy_fill_reply,
> .cleanup_data = phy_cleanup_data,
> + .dump_one_dev = ethnl_perphy_dump_one_dev,
> };
> diff --git a/net/ethtool/plca.c b/net/ethtool/plca.c
> index 91f0c4233298..84e902532617 100644
> --- a/net/ethtool/plca.c
> +++ b/net/ethtool/plca.c
> @@ -188,6 +188,7 @@ const struct ethnl_request_ops ethnl_plca_cfg_request_ops = {
> .prepare_data = plca_get_cfg_prepare_data,
> .reply_size = plca_get_cfg_reply_size,
> .fill_reply = plca_get_cfg_fill_reply,
> + .dump_one_dev = ethnl_perphy_dump_one_dev,
>
> .set = ethnl_set_plca,
> .set_ntf_cmd = ETHTOOL_MSG_PLCA_NTF,
> @@ -268,4 +269,5 @@ const struct ethnl_request_ops ethnl_plca_status_request_ops = {
> .prepare_data = plca_get_status_prepare_data,
> .reply_size = plca_get_status_reply_size,
> .fill_reply = plca_get_status_fill_reply,
> + .dump_one_dev = ethnl_perphy_dump_one_dev,
> };
> diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
> index 2eb9bdc2dcb9..83f0205053a3 100644
> --- a/net/ethtool/pse-pd.c
> +++ b/net/ethtool/pse-pd.c
> @@ -338,6 +338,7 @@ const struct ethnl_request_ops ethnl_pse_request_ops = {
> .reply_size = pse_reply_size,
> .fill_reply = pse_fill_reply,
> .cleanup_data = pse_cleanup_data,
> + .dump_one_dev = ethnl_perphy_dump_one_dev,
>
> .set = ethnl_set_pse,
> /* PSE has no notification */
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
@ 2026-03-26 8:10 ` Maxime Chevallier
2026-03-26 8:55 ` Björn Töpel
2026-03-26 22:22 ` Jakub Kicinski
2026-03-26 22:23 ` Jakub Kicinski
2 siblings, 1 reply; 23+ messages in thread
From: Maxime Chevallier @ 2026-03-26 8:10 UTC (permalink / raw)
To: Björn Töpel, netdev, David S. Miller, Andrew Lunn,
Donald Hunter, Eric Dumazet, Jakub Kicinski, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman
Cc: Danielle Ratson, Hariprasad Kelam, Ido Schimmel, Kory Maincent,
Leon Romanovsky, Michael Chan, Oleksij Rempel, Pavan Chebbi,
Piergiorgio Beruto, Russell King, Saeed Mahameed, Shuah Khan,
Tariq Toukan, Willem de Bruijn, Kees Cook, linux-kernel,
linux-kselftest, linux-rdma
Hi Björn,
On 25/03/2026 15:50, Björn Töpel wrote:
> Add the netlink YAML spec and auto-generated UAPI header for a unified
> loopback interface covering MAC, PHY, and pluggable module components.
>
> Each loopback point is described by a nested entry attribute
Is the nest actually needed ? if everything is under the nest, you might
as well just drop it and return all the attributes directly ?
Maxime
> containing:
>
> - component where in the path (MAC, PHY, MODULE)
> - name subsystem label, e.g. "cmis-host" or "cmis-media"
> - id optional instance selector (e.g. PHY id, port id)
> - depth ordering index within a component (0 = first/only)
> - supported bitmask of supported directions
> - direction LOCAL, REMOTE, or 0 (disabled)
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
>
Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions
2026-03-26 8:10 ` Maxime Chevallier
@ 2026-03-26 8:55 ` Björn Töpel
0 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-26 8:55 UTC (permalink / raw)
To: Maxime Chevallier
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Naveen Mamindlapalli, Paolo Abeni, Simon Horman,
Danielle Ratson, Hariprasad Kelam, Ido Schimmel, Kory Maincent,
Leon Romanovsky, Michael Chan, Oleksij Rempel, Pavan Chebbi,
Piergiorgio Beruto, Russell King, Saeed Mahameed, Shuah Khan,
Tariq Toukan, Willem de Bruijn, Kees Cook, linux-kernel,
linux-kselftest, linux-rdma
Maxime!
On Thu, 26 Mar 2026 at 09:10, Maxime Chevallier
<maxime.chevallier@bootlin.com> wrote:
>
> Hi Björn,
>
> On 25/03/2026 15:50, Björn Töpel wrote:
> > Add the netlink YAML spec and auto-generated UAPI header for a unified
> > loopback interface covering MAC, PHY, and pluggable module components.
> >
> > Each loopback point is described by a nested entry attribute
>
> Is the nest actually needed ? if everything is under the nest, you might
> as well just drop it and return all the attributes directly ?
Hmm, you're right! This is a leftover pre the dumpit change. GET/SET
never needs nesting. I'll fix that for the next round!
Thanks!
Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test
2026-03-25 14:50 ` [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test Björn Töpel
@ 2026-03-26 9:32 ` Breno Leitao
2026-03-26 9:44 ` Björn Töpel
0 siblings, 1 reply; 23+ messages in thread
From: Breno Leitao @ 2026-03-26 9:32 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
On Wed, Mar 25, 2026 at 03:50:16PM +0100, Björn Töpel wrote:
> +def _dfs_write_u32(cfg, path, val):
> + with open(os.path.join(_nsim_dfs_path(cfg), path), "w",
> + encoding="utf-8") as f:
> + f.write(str(val))
This function doesn't seem to be called, right?
> +def main() -> None:
> + """Run netdevsim loopback tests."""
> + with NetDrvEnv(__file__) as cfg:
> + cfg.ethnl = EthtoolFamily()
Given some functions above assume this is nsim, should you call
cfg.require_nsim() ?
Reviewed-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation
2026-03-25 14:50 ` [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation Björn Töpel
@ 2026-03-26 9:40 ` Breno Leitao
0 siblings, 0 replies; 23+ messages in thread
From: Breno Leitao @ 2026-03-26 9:40 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
On Wed, Mar 25, 2026 at 03:50:15PM +0100, Björn Töpel wrote:
> Implement the three ethtool loopback ops for MAC-level loopback
> simulation:
>
> - get_loopback(): exact lookup by name ("mac")
> - get_loopback_by()_index: enumerate (single entry at index 0)
- get_loopback_by_index(): enumerate (single entry at index 0)
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test
2026-03-26 9:32 ` Breno Leitao
@ 2026-03-26 9:44 ` Björn Töpel
0 siblings, 0 replies; 23+ messages in thread
From: Björn Töpel @ 2026-03-26 9:44 UTC (permalink / raw)
To: Breno Leitao
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
Breno!
On Thu, 26 Mar 2026 at 10:33, Breno Leitao <leitao@debian.org> wrote:
>
> On Wed, Mar 25, 2026 at 03:50:16PM +0100, Björn Töpel wrote:
>
> > +def _dfs_write_u32(cfg, path, val):
> > + with open(os.path.join(_nsim_dfs_path(cfg), path), "w",
> > + encoding="utf-8") as f:
> > + f.write(str(val))
>
> This function doesn't seem to be called, right?
Indeed -- another leftover! Thank you!
> > +def main() -> None:
> > + """Run netdevsim loopback tests."""
> > + with NetDrvEnv(__file__) as cfg:
> > + cfg.ethnl = EthtoolFamily()
>
> Given some functions above assume this is nsim, should you call
> cfg.require_nsim() ?
Yeah, or directly from NetDrvEnv() statement above! Good point! Will fix!
Björn
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops
2026-03-25 14:50 ` [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops Björn Töpel
@ 2026-03-26 9:49 ` Breno Leitao
0 siblings, 0 replies; 23+ messages in thread
From: Breno Leitao @ 2026-03-26 9:49 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Maxime Chevallier, Naveen Mamindlapalli,
Paolo Abeni, Simon Horman, Danielle Ratson, Hariprasad Kelam,
Ido Schimmel, Kory Maincent, Leon Romanovsky, Michael Chan,
Oleksij Rempel, Pavan Chebbi, Piergiorgio Beruto, Russell King,
Saeed Mahameed, Shuah Khan, Tariq Toukan, Willem de Bruijn,
Kees Cook, linux-kernel, linux-kselftest, linux-rdma
On Wed, Mar 25, 2026 at 03:50:14PM +0100, Björn Töpel wrote:
> @@ -284,20 +305,31 @@ static int loopback_dump_one_dev(struct sk_buff *skb,
> {
> struct loopback_req_info *req_info =
> container_of(ctx->req_info, struct loopback_req_info, base);
> + /* pos_sub encodes: upper 16 bits = component phase, lower 16 = index
> + * within that component. dump_one_dev is called repeatedly with
> + * increasing pos_sub until all components are exhausted.
> + */
> + enum ethtool_loopback_component phase = *pos_sub >> 16;
> + u32 idx = *pos_sub & 0xffff;
Consider introducing macros for these bit operations to improve code
readability. Named macros would make the shift and mask operations more
self-documenting and facilitate future changes, if we eventually get
there.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-26 8:10 ` Maxime Chevallier
@ 2026-03-26 22:22 ` Jakub Kicinski
2026-03-26 22:23 ` Jakub Kicinski
2 siblings, 0 replies; 23+ messages in thread
From: Jakub Kicinski @ 2026-03-26 22:22 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Maxime Chevallier, Naveen Mamindlapalli, Paolo Abeni,
Simon Horman, Danielle Ratson, Hariprasad Kelam, Ido Schimmel,
Kory Maincent, Leon Romanovsky, Michael Chan, Oleksij Rempel,
Pavan Chebbi, Piergiorgio Beruto, Russell King, Saeed Mahameed,
Shuah Khan, Tariq Toukan, Willem de Bruijn, Kees Cook,
linux-kernel, linux-kselftest, linux-rdma
On Wed, 25 Mar 2026 15:50:10 +0100 Björn Töpel wrote:
> + -
> + name: unspec
> + type: unused
> + value: 0
I don't think we need to add unspec attrs for new sets ?
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-26 8:10 ` Maxime Chevallier
2026-03-26 22:22 ` Jakub Kicinski
@ 2026-03-26 22:23 ` Jakub Kicinski
2 siblings, 0 replies; 23+ messages in thread
From: Jakub Kicinski @ 2026-03-26 22:23 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, David S. Miller, Andrew Lunn, Donald Hunter, Eric Dumazet,
Maxime Chevallier, Naveen Mamindlapalli, Paolo Abeni,
Simon Horman, Danielle Ratson, Hariprasad Kelam, Ido Schimmel,
Kory Maincent, Leon Romanovsky, Michael Chan, Oleksij Rempel,
Pavan Chebbi, Piergiorgio Beruto, Russell King, Saeed Mahameed,
Shuah Khan, Tariq Toukan, Willem de Bruijn, Kees Cook,
linux-kernel, linux-kselftest, linux-rdma
On Wed, 25 Mar 2026 15:50:10 +0100 Björn Töpel wrote:
> + -
> + name: depth
> + type: u8
> + doc: |
> + Ordering index within a component instance. When a component
> + has multiple loopback points of the same type (e.g. two PCS
> + blocks inside a rate-adaptation PHY), depth distinguishes
> + them. Lower depth values are closer to the host side, higher
> + values are closer to the line/media side. Defaults to 0 when
> + there is only one loopback point per (component, name) tuple.
> + -
> + name: supported
> + type: u8
> + enum: loopback-direction
> + enum-as-flags: true
> + doc: Bitmask of supported loopback directions
> + -
> + name: direction
> + type: u8
> + enum: loopback-direction
> + doc: Current loopback direction, 0 means disabled
u32, Netlink attrs are padded to 4B anyway
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-03-26 22:23 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-25 14:50 [PATCH net-next v2 00/12] ethtool: Generic loopback support Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 01/12] ethtool: Add dump_one_dev callback for per-device sub-iteration Björn Töpel
2026-03-25 18:20 ` Maxime Chevallier
2026-03-25 14:50 ` [PATCH net-next v2 02/12] ethtool: Convert per-PHY commands to dump_one_dev Björn Töpel
2026-03-25 18:21 ` Maxime Chevallier
2026-03-25 14:50 ` [PATCH net-next v2 03/12] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-26 8:10 ` Maxime Chevallier
2026-03-26 8:55 ` Björn Töpel
2026-03-26 22:22 ` Jakub Kicinski
2026-03-26 22:23 ` Jakub Kicinski
2026-03-25 14:50 ` [PATCH net-next v2 04/12] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 05/12] ethtool: Add CMIS loopback helpers for module loopback control Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 06/12] selftests: drv-net: Add loopback driver test Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 07/12] ethtool: Add MAC loopback support via ethtool_ops Björn Töpel
2026-03-26 9:49 ` Breno Leitao
2026-03-25 14:50 ` [PATCH net-next v2 08/12] netdevsim: Add MAC loopback simulation Björn Töpel
2026-03-26 9:40 ` Breno Leitao
2026-03-25 14:50 ` [PATCH net-next v2 09/12] selftests: drv-net: Add MAC loopback netdevsim test Björn Töpel
2026-03-26 9:32 ` Breno Leitao
2026-03-26 9:44 ` Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 10/12] MAINTAINERS: Add entry for ethtool loopback Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 11/12] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
2026-03-25 14:50 ` [PATCH net-next v2 12/12] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox