From: "Björn Töpel" <bjorn@kernel.org>
To: netdev@vger.kernel.org, Donald Hunter <donald.hunter@gmail.com>,
Jakub Kicinski <kuba@kernel.org>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
Saeed Mahameed <saeedm@nvidia.com>,
Tariq Toukan <tariqt@nvidia.com>,
Leon Romanovsky <leon@kernel.org>,
Andrew Lunn <andrew+netdev@lunn.ch>
Cc: "Björn Töpel" <bjorn@kernel.org>,
"Maxime Chevallier" <maxime.chevallier@bootlin.com>,
"Andrew Lunn" <andrew@lunn.ch>,
"Michael Chan" <michael.chan@broadcom.com>,
"Hariprasad Kelam" <hkelam@marvell.com>,
"Ido Schimmel" <idosch@nvidia.com>,
"Danielle Ratson" <danieller@nvidia.com>,
linux-kernel@vger.kernel.org, linux-rdma@vger.kernel.org,
"Russell King" <linux@armlinux.org.uk>
Subject: [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test
Date: Sun, 8 Mar 2026 13:40:10 +0100 [thread overview]
Message-ID: <20260308124016.3134012-5-bjorn@kernel.org> (raw)
In-Reply-To: <20260308124016.3134012-1-bjorn@kernel.org>
Add a selftest for the ethtool loopback UAPI exercising module
loopback via the loopback GET/SET netlink commands.
Works on any device that reports module loopback entries. Tests cover
enable near-end and far-end, disable, direction switching (mutual
exclusivity), idempotent enable, and rejection while interface is up.
Devices without module loopback support are skipped.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../selftests/drivers/net/hw/loopback_drv.py | 227 ++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py
diff --git a/tools/testing/selftests/drivers/net/hw/loopback_drv.py b/tools/testing/selftests/drivers/net/hw/loopback_drv.py
new file mode 100755
index 000000000000..ab105664e07e
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/loopback_drv.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Tests for ethtool loopback GET/SET with CMIS modules.
+
+Works on any device that reports module loopback entries. On devices
+without CMIS loopback support, tests are skipped.
+"""
+
+import errno
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import KsftSkipEx, KsftFailEx, ksft_disruptive
+from lib.py import EthtoolFamily, NlError
+from lib.py import NetDrvEnv, ip, defer
+
+# Direction flags as YNL returns them (sets of flag name strings)
+DIR_NONE = set()
+DIR_NEAR_END = {'near-end'}
+DIR_FAR_END = {'far-end'}
+
+
+def _get_loopback(cfg):
+ """GET loopback and return the list of entries."""
+ result = cfg.ethnl.loopback_get({
+ 'header': {'dev-index': cfg.ifindex}
+ })
+ return result.get('entry', [])
+
+
+def _set_loopback(cfg, component, name, direction):
+ """SET loopback for a single entry."""
+ cfg.ethnl.loopback_set({
+ 'header': {'dev-index': cfg.ifindex},
+ 'entry': [{
+ 'component': component,
+ 'name': name,
+ 'direction': direction,
+ }]
+ })
+
+
+def _require_module_entries(cfg):
+ """Return module loopback entries, skip if none available."""
+ try:
+ entries = _get_loopback(cfg)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("Device does not support loopback")
+ raise
+ mod_entries = [e for e in entries if e['component'] == 'module']
+ if not mod_entries:
+ raise KsftSkipEx("No module loopback entries")
+ return mod_entries
+
+
+@ksft_disruptive
+def test_set_near_end(cfg):
+ """SET a module entry to near-end and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'near-end' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END)
+
+
+@ksft_disruptive
+def test_set_far_end(cfg):
+ """SET a module entry to far-end and verify via GET."""
+ mod_entries = _require_module_entries(cfg)
+
+ far = [e for e in mod_entries
+ if 'far-end' in e['supported']]
+ if not far:
+ raise KsftSkipEx("No far-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = far[0]
+ _set_loopback(cfg, 'module', target['name'], 'far-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'far-end' in e['supported']]
+ ksft_eq(len(updated), 1)
+ ksft_eq(updated[0]['direction'], DIR_FAR_END)
+
+
+@ksft_disruptive
+def test_set_disable(cfg):
+ """Enable then disable loopback and verify."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ # Disable
+ _set_loopback(cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_NONE,
+ "Direction should be off after disable")
+
+
+@ksft_disruptive
+def test_set_direction_switch(cfg):
+ """Enable near-end, then switch to far-end. The kernel must disable
+ near-end before enabling far-end (mutual exclusivity).
+ """
+ mod_entries = _require_module_entries(cfg)
+
+ both = [e for e in mod_entries
+ if 'near-end' in e['supported'] and 'far-end' in e['supported']]
+ if not both:
+ raise KsftSkipEx("No entry with both near-end and far-end support")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = both[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END)
+
+ # Switch to far-end
+ _set_loopback(cfg, 'module', target['name'], 'far-end')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries if e['name'] == target['name']]
+ ksft_eq(updated[0]['direction'], DIR_FAR_END,
+ "Should have switched to far-end")
+
+
+@ksft_disruptive
+def test_set_idempotent(cfg):
+ """Enable the same direction twice. Second call should not fail."""
+ mod_entries = _require_module_entries(cfg)
+
+ near = [e for e in mod_entries
+ if 'near-end' in e['supported']]
+ if not near:
+ raise KsftSkipEx("No near-end capable module entry")
+
+ ip(f"link set dev {cfg.ifname} down")
+ defer(ip, f"link set dev {cfg.ifname} up")
+
+ target = near[0]
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+ defer(_set_loopback, cfg, 'module', target['name'], 0)
+
+ # Second enable of the same direction should succeed
+ _set_loopback(cfg, 'module', target['name'], 'near-end')
+
+ entries = _get_loopback(cfg)
+ updated = [e for e in entries
+ if e['name'] == target['name']
+ and 'near-end' in e['supported']]
+ ksft_eq(updated[0]['direction'], DIR_NEAR_END,
+ "Direction should still be near-end")
+
+
+@ksft_disruptive
+def test_set_while_up(cfg):
+ """SET while interface is UP should fail."""
+ mod_entries = _require_module_entries(cfg)
+
+ target = mod_entries[0]
+ direction = 'near-end'
+ if direction not in target['supported']:
+ direction = 'far-end'
+
+ try:
+ _set_loopback(cfg, 'module', target['name'], direction)
+ raise KsftFailEx("Should have rejected SET while interface is up")
+ except NlError as e:
+ ksft_eq(e.error, errno.EBUSY,
+ "Expected EBUSY when interface is up")
+
+
+def main() -> None:
+ with NetDrvEnv(__file__, nsim_test=False) as cfg:
+ cfg.ethnl = EthtoolFamily()
+
+ ksft_run([
+ test_set_near_end,
+ test_set_far_end,
+ test_set_disable,
+ test_set_direction_switch,
+ test_set_idempotent,
+ test_set_while_up,
+ ], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.53.0
next prev parent reply other threads:[~2026-03-08 12:40 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-09 14:16 ` Maxime Chevallier
2026-03-09 14:59 ` Björn Töpel
2026-03-09 16:45 ` Andrew Lunn
2026-03-10 10:23 ` Maxime Chevallier
2026-03-10 13:56 ` Andrew Lunn
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
2026-03-09 7:34 ` Maxime Chevallier
2026-03-09 8:21 ` Björn Töpel
2026-03-09 14:51 ` Björn Töpel
2026-03-09 16:14 ` Maxime Chevallier
2026-03-08 12:40 ` [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control Björn Töpel
2026-03-08 12:40 ` Björn Töpel [this message]
2026-03-08 12:40 ` [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
2026-03-09 3:56 ` kernel test robot
2026-03-08 12:40 ` [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
2026-03-09 14:55 ` Björn Töpel
2026-03-10 7:35 ` Naveen Mamindlapalli
2026-03-10 14:00 ` Andrew Lunn
2026-03-11 5:59 ` Naveen Mamindlapalli
2026-03-11 12:32 ` Andrew Lunn
2026-03-11 16:52 ` Russell King (Oracle)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260308124016.3134012-5-bjorn@kernel.org \
--to=bjorn@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=danieller@nvidia.com \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=edumazet@google.com \
--cc=hkelam@marvell.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=maxime.chevallier@bootlin.com \
--cc=michael.chan@broadcom.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=saeedm@nvidia.com \
--cc=tariqt@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.