public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
From: "Björn Töpel" <bjorn@kernel.org>
To: netdev@vger.kernel.org, "David S. Miller" <davem@davemloft.net>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	Donald Hunter <donald.hunter@gmail.com>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>,
	Maxime Chevallier <maxime.chevallier@bootlin.com>,
	Naveen Mamindlapalli <naveenm@marvell.com>,
	Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>
Cc: "Björn Töpel" <bjorn@kernel.org>,
	"Danielle Ratson" <danieller@nvidia.com>,
	"Hariprasad Kelam" <hkelam@marvell.com>,
	"Ido Schimmel" <idosch@nvidia.com>,
	"Kory Maincent" <kory.maincent@bootlin.com>,
	"Leon Romanovsky" <leon@kernel.org>,
	"Michael Chan" <michael.chan@broadcom.com>,
	"Oleksij Rempel" <o.rempel@pengutronix.de>,
	"Pavan Chebbi" <pavan.chebbi@broadcom.com>,
	"Piergiorgio Beruto" <piergiorgio.beruto@gmail.com>,
	"Russell King" <linux@armlinux.org.uk>,
	"Saeed Mahameed" <saeedm@nvidia.com>,
	"Shuah Khan" <shuah@kernel.org>,
	"Tariq Toukan" <tariqt@nvidia.com>,
	"Willem de Bruijn" <willemb@google.com>,
	"Kees Cook" <kees@kernel.org>,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
	linux-rdma@vger.kernel.org
Subject: [PATCH net-next v2 06/12] selftests: drv-net: Add loopback driver test
Date: Wed, 25 Mar 2026 15:50:13 +0100	[thread overview]
Message-ID: <20260325145022.2607545-7-bjorn@kernel.org> (raw)
In-Reply-To: <20260325145022.2607545-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 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


  parent reply	other threads:[~2026-03-25 14:51 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Björn Töpel [this message]
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

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=20260325145022.2607545-7-bjorn@kernel.org \
    --to=bjorn@kernel.org \
    --cc=andrew+netdev@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=kees@kernel.org \
    --cc=kory.maincent@bootlin.com \
    --cc=kuba@kernel.org \
    --cc=leon@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@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=naveenm@marvell.com \
    --cc=netdev@vger.kernel.org \
    --cc=o.rempel@pengutronix.de \
    --cc=pabeni@redhat.com \
    --cc=pavan.chebbi@broadcom.com \
    --cc=piergiorgio.beruto@gmail.com \
    --cc=saeedm@nvidia.com \
    --cc=shuah@kernel.org \
    --cc=tariqt@nvidia.com \
    --cc=willemb@google.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox