summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-09-24 08:28:31 -0700
committerJakub Kicinski <kuba@kernel.org>2026-09-24 08:28:31 -0700
commit447cb143d024d97257cefcdb2192e4b7d2fe47ac (patch)
treee93a07bff19555b114ec308e8404556a5c6cdf55
parent1c85ea5df86bed136589a2866c22f394020d3fdf (diff)
parentefc7f4646c1d4d45dd4cdab6a94275484e3287d0 (diff)
Merge branch 'selftests-drv-net-add-vlan-test'
Ovidiu Panait says: ==================== selftests: drv-net: Add VLAN test This series adds a selftest that pings over 802.1q, 802.1ad and QinQ VLAN interfaces with RX VLAN stripping offloads enabled and disabled. This series contains only the selftest part of [1]. [1] https://lore.kernel.org/20260908164309.59282-1-ovidiu.panait.rb@renesas.com ==================== Link: https://patch.msgid.link/20260918112529.96039-1-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--tools/testing/selftests/drivers/net/gro_lib.py66
-rw-r--r--tools/testing/selftests/drivers/net/hw/Makefile1
-rw-r--r--tools/testing/selftests/drivers/net/hw/config1
-rw-r--r--tools/testing/selftests/drivers/net/hw/lib/py/__init__.py3
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/vlan.py127
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/__init__.py3
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/feat.py43
7 files changed, 197 insertions, 47 deletions
diff --git a/tools/testing/selftests/drivers/net/gro_lib.py b/tools/testing/selftests/drivers/net/gro_lib.py
index abecf60baa53..875a715b2ae1 100644
--- a/tools/testing/selftests/drivers/net/gro_lib.py
+++ b/tools/testing/selftests/drivers/net/gro_lib.py
@@ -52,7 +52,7 @@ import re
from lib.py import ksft_run, ksft_exit, ksft_pr, ksft_eq
from lib.py import NetDrvEpEnv, KsftFailEx, KsftSkipEx, KsftXfailEx
from lib.py import NetdevFamily, EthtoolFamily
-from lib.py import bkg, cmd, ctl_file_write, defer, ethtool, ip
+from lib.py import bkg, cmd, ctl_file_write, defer, ethtool, ip, set_ethtool_feat
from lib.py import ksft_variants, KsftNamedVariant
@@ -125,31 +125,6 @@ def _set_gro_size_restore(cfg, size):
comment="gro_ipv4_max_size not applied")
-def _set_ethtool_feat(dev, current, feats, host=None):
- s2n = {True: "on", False: "off"}
-
- new = ["-K", dev]
- old = ["-K", dev]
- no_change = True
- for name, state in feats.items():
- new += [name, s2n[state]]
- old += [name, s2n[current[name]["active"]]]
-
- if current[name]["active"] != state:
- no_change = False
- if current[name]["fixed"]:
- raise KsftXfailEx(f"Device does not support {name}")
- if no_change:
- return
-
- eth_cmd = ethtool(" ".join(new), host=host)
- defer(ethtool, " ".join(old), host=host)
-
- # If ethtool printed something kernel must have modified some features
- if eth_cmd.stdout:
- ksft_pr(eth_cmd)
-
-
def _get_queue_stats(cfg, queue_id):
"""Get stats for a specific Rx queue."""
cfg.wait_hw_stats_settle()
@@ -280,15 +255,16 @@ def _setup(cfg, mode, test_name):
ctl_file_write(flush_path, "200000")
ctl_file_write(irq_path, "10")
- _set_ethtool_feat(cfg.ifname, cfg.feat,
- {"generic-receive-offload": True,
- "rx-gro-hw": False,
- "large-receive-offload": False})
+ set_ethtool_feat(cfg.ifname, cfg.feat,
+ {"generic-receive-offload": True,
+ "rx-gro-hw": False,
+ "large-receive-offload": False})
elif mode == "hw":
- _set_ethtool_feat(cfg.ifname, cfg.feat,
- {"generic-receive-offload": False,
- "rx-gro-hw": True,
- "large-receive-offload": False})
+ set_ethtool_feat(cfg.ifname, cfg.feat,
+ {"generic-receive-offload": False,
+ "rx-gro-hw": True,
+ "large-receive-offload": False},
+ check=False)
# Some NICs treat HW GRO as a GRO sub-feature so disabling GRO
# will also clear HW GRO. Use a hack of installing XDP generic
@@ -303,27 +279,27 @@ def _setup(cfg, mode, test_name):
# Attaching XDP may change features, fetch the latest state
feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
- _set_ethtool_feat(cfg.ifname, feat,
- {"generic-receive-offload": True,
- "rx-gro-hw": True,
- "large-receive-offload": False})
+ set_ethtool_feat(cfg.ifname, feat,
+ {"generic-receive-offload": True,
+ "rx-gro-hw": True,
+ "large-receive-offload": False})
elif mode == "lro":
# netdevsim advertises LRO for feature inheritance testing with
# bonding/team tests but it doesn't actually perform the offload
cfg.require_nsim(nsim_test=False)
- _set_ethtool_feat(cfg.ifname, cfg.feat,
- {"generic-receive-offload": False,
- "rx-gro-hw": False,
- "large-receive-offload": True})
+ set_ethtool_feat(cfg.ifname, cfg.feat,
+ {"generic-receive-offload": False,
+ "rx-gro-hw": False,
+ "large-receive-offload": True})
try:
# Disable TSO for local tests
cfg.require_nsim() # will raise KsftXfailEx if not running on nsim
- _set_ethtool_feat(cfg.remote_ifname, cfg.remote_feat,
- {"tcp-segmentation-offload": False},
- host=cfg.remote)
+ set_ethtool_feat(cfg.remote_ifname, cfg.remote_feat,
+ {"tcp-segmentation-offload": False},
+ host=cfg.remote)
except KsftXfailEx:
pass
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 8aebdc6feb17..18ce235a77f8 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -50,6 +50,7 @@ TEST_PROGS = \
tso.py \
userns_devmem.py \
uso.py \
+ vlan.py \
xdp_metadata.py \
xsk_reconfig.py \
#
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index d89a9ba17655..c6c2b64bb712 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -24,5 +24,6 @@ CONFIG_NET_SCH_INGRESS=y
CONFIG_SYNC_FILE=y
CONFIG_UDMABUF=y
CONFIG_USER_NS=y
+CONFIG_VLAN_8021Q=m
CONFIG_VXLAN=y
CONFIG_XFRM_USER=y
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
index 81e1d1865cd5..4077f5f1a1be 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
@@ -31,6 +31,7 @@ try:
ksft_setup, ksft_variants, KsftNamedVariant
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
+ from drivers.net.lib.py import set_ethtool_feat
from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
@@ -49,7 +50,7 @@ try:
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
"ksft_not_none", "ksft_not_none",
"NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
- "Remote", "Iperf3Runner"]
+ "Remote", "Iperf3Runner", "set_ethtool_feat"]
except ModuleNotFoundError as e:
print("Failed importing `net` library from kernel sources")
print(str(e))
diff --git a/tools/testing/selftests/drivers/net/hw/vlan.py b/tools/testing/selftests/drivers/net/hw/vlan.py
new file mode 100755
index 000000000000..00c90b930f67
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/vlan.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+VLAN tests.
+
+Validates that ping traffic is sent and received correctly over 802.1q
+and 802.1ad VLAN interfaces, with hardware RX VLAN stripping enabled and
+disabled on the local interface.
+
+The "hw" test variants enable the RX VLAN stripping features supported by
+the device, the "sw" test variants disable all of them. A test is xfailed
+if the requested configuration is not possible.
+
+Test cases:
+ - 8021q_hw: Traffic over a single 802.1q VLAN, HW stripping on
+ - 8021q_sw: Traffic over a single 802.1q VLAN, HW stripping off
+ - 8021ad_hw: Traffic over a single 802.1ad VLAN, HW stripping on
+ - 8021ad_sw: Traffic over a single 802.1ad VLAN, HW stripping off
+ - qinq_hw: Traffic over an 802.1q VLAN stacked on an 802.1ad VLAN,
+ HW stripping on
+ - qinq_sw: Traffic over an 802.1q VLAN stacked on an 802.1ad VLAN,
+ HW stripping off
+"""
+
+import os
+
+from lib.py import (
+ KsftNamedVariant,
+ KsftXfailEx,
+ NetDrvEpEnv,
+ cmd,
+ defer,
+ ethtool,
+ ip,
+ ksft_exit,
+ ksft_run,
+ ksft_variants,
+ set_ethtool_feat,
+)
+
+OUTER_DEV = f"vlout{os.getpid()}"
+INNER_DEV = f"vlin{os.getpid()}"
+
+OUTER_VID = 100
+INNER_VID = 200
+
+LOCAL_IP = os.environ.get("VLAN_LOCAL_V4", "198.51.100.1")
+REMOTE_IP = os.environ.get("VLAN_REMOTE_V4", "198.51.100.2")
+
+
+def _vlan_add(base, name, proto, vid, host=None):
+ """Create a VLAN device on top of base and bring it up."""
+
+ ip(f"link add link {base} name {name} type vlan proto {proto} id {vid}",
+ host=host)
+ defer(ip, f"link del {name}", host=host)
+ ip(f"link set {name} up", host=host)
+
+
+def _vlan_setup(base, addr, outer_proto, inner_proto, host=None):
+ """Create VLAN interfaces on base and set an IP on the innermost one."""
+
+ _vlan_add(base, OUTER_DEV, outer_proto, OUTER_VID, host=host)
+ if inner_proto:
+ _vlan_add(OUTER_DEV, INNER_DEV, inner_proto, INNER_VID, host=host)
+
+ dev = INNER_DEV if inner_proto else OUTER_DEV
+ ip(f"addr add {addr}/24 dev {dev}", host=host)
+
+
+def _setup(cfg, outer_proto, inner_proto, hw_strip):
+ """Configure VLAN stripping and create the VLAN interfaces."""
+
+ names = ["rx-vlan-offload"]
+ if outer_proto == "802.1ad":
+ names.append("rx-vlan-stag-hw-parse")
+
+ feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ feats = {}
+ for name in names:
+ # hw: skip the features the device does not support
+ # sw: request all of them off
+ if hw_strip and feat[name]["fixed"] and not feat[name]["active"]:
+ continue
+ feats[name] = hw_strip
+
+ if hw_strip and not feats:
+ raise KsftXfailEx("Device does not support RX VLAN stripping")
+
+ set_ethtool_feat(cfg.ifname, feat, feats)
+
+ _vlan_setup(cfg.ifname, LOCAL_IP, outer_proto, inner_proto)
+ _vlan_setup(cfg.remote_ifname, REMOTE_IP, outer_proto, inner_proto,
+ host=cfg.remote)
+
+
+def _vlan_variants():
+ """Generator that yields the VLAN protocols and the stripping mode."""
+
+ yield KsftNamedVariant("8021q_hw", "802.1q", None, True)
+ yield KsftNamedVariant("8021q_sw", "802.1q", None, False)
+ yield KsftNamedVariant("8021ad_hw", "802.1ad", None, True)
+ yield KsftNamedVariant("8021ad_sw", "802.1ad", None, False)
+ yield KsftNamedVariant("qinq_hw", "802.1ad", "802.1q", True)
+ yield KsftNamedVariant("qinq_sw", "802.1ad", "802.1q", False)
+
+
+@ksft_variants(_vlan_variants())
+def test(cfg, outer_proto, inner_proto, hw_strip):
+ """Run a single VLAN test"""
+
+ _setup(cfg, outer_proto, inner_proto, hw_strip)
+
+ cmd(f"ping -c 1 -W 5 {REMOTE_IP}")
+
+
+def main() -> None:
+ """ Ksft boiler plate main """
+
+ with NetDrvEpEnv(__file__) as cfg:
+ ksft_run(cases=[test], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index 591b1e6c7eea..40ccdcf0c378 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -48,11 +48,12 @@ try:
"ksft_not_none", "ksft_not_none"]
from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
+ from .feat import set_ethtool_feat
from .load import GenerateTraffic, Iperf3Runner
from .remote import Remote
__all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
- "Remote", "Iperf3Runner"]
+ "Remote", "Iperf3Runner", "set_ethtool_feat"]
except ModuleNotFoundError as e:
print("Failed importing `net` library from kernel sources")
print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/feat.py b/tools/testing/selftests/drivers/net/lib/py/feat.py
new file mode 100644
index 000000000000..f0630844f023
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/feat.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Netdev feature helper utilities for kernel selftests.
+
+Provides common operations for changing device features via ethtool.
+"""
+
+from . import KsftXfailEx, defer, ethtool, ksft_pr
+
+
+def set_ethtool_feat(dev, current, feats, host=None, check=True):
+ """Set ethtool features with defer to restore original state."""
+ s2n = {True: "on", False: "off"}
+
+ new = ["-K", dev]
+ old = ["-K", dev]
+ no_change = True
+ for name, state in feats.items():
+ new += [name, s2n[state]]
+ old += [name, s2n[current[name]["active"]]]
+
+ if current[name]["active"] != state:
+ no_change = False
+ if current[name]["fixed"]:
+ raise KsftXfailEx(f"Device does not support {name}")
+ if no_change:
+ return
+
+ eth_cmd = ethtool(" ".join(new), host=host)
+ defer(ethtool, " ".join(old), host=host)
+
+ # If ethtool printed something kernel must have modified some features
+ if eth_cmd.stdout:
+ ksft_pr(eth_cmd)
+
+ if not check:
+ return
+
+ current = ethtool(f"-k {dev}", json=True, host=host)[0]
+ for name, state in feats.items():
+ if current[name]["active"] != state:
+ raise KsftXfailEx(f"{name} was not set to {s2n[state]}")