summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/drivers/net/hw
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/drivers/net/hw')
-rw-r--r--tools/testing/selftests/drivers/net/hw/Makefile10
-rw-r--r--tools/testing/selftests/drivers/net/hw/config12
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/csum.py2
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py296
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/devmem.py82
-rw-r--r--tools/testing/selftests/drivers/net/hw/devmem_lib.py316
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ethtool_rmon.sh2
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh6
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/gro_hw.py10
-rw-r--r--tools/testing/selftests/drivers/net/hw/iou-zcrx.c6
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/iou-zcrx.py38
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ipsec_vxlan.py204
-rw-r--r--tools/testing/selftests/drivers/net/hw/lib/py/__init__.py4
-rw-r--r--tools/testing/selftests/drivers/net/hw/ncdevmem.c94
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/nk_devmem.py52
-rw-r--r--tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c39
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/nk_qlease.py306
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ntuple.py5
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/rss_ctx.py109
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/toeplitz.py22
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/tso.py21
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/userns_devmem.py49
22 files changed, 1513 insertions, 172 deletions
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 85ca4d1ecf9e..78bb0169350b 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -19,7 +19,7 @@ TEST_GEN_FILES := \
TEST_PROGS = \
csum.py \
- devlink_port_split.py \
+ devlink_rate_cross_esw.py \
devlink_rate_tc_bw.py \
devmem.py \
ethtool.sh \
@@ -31,9 +31,11 @@ TEST_PROGS = \
hw_stats_l3.sh \
hw_stats_l3_gre.sh \
iou-zcrx.py \
+ ipsec_vxlan.py \
irq.py \
loopback.sh \
nic_timestamp.py \
+ nk_devmem.py \
nk_netns.py \
nk_qlease.py \
ntuple.py \
@@ -45,12 +47,18 @@ TEST_PROGS = \
rss_input_xfrm.py \
toeplitz.py \
tso.py \
+ userns_devmem.py \
uso.py \
xdp_metadata.py \
xsk_reconfig.py \
#
+TEST_PROGS_EXTENDED := \
+ devlink_port_split.py \
+# end of TEST_PROGS_EXTENDED
+
TEST_FILES := \
+ devmem_lib.py \
ethtool_lib.sh \
#
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index dd50cb8a7911..d89a9ba17655 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -3,14 +3,26 @@ CONFIG_FAIL_FUNCTION=y
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FUNCTION_ERROR_INJECTION=y
+CONFIG_HUGETLBFS=y
+CONFIG_INET6_ESP=y
+CONFIG_INET6_ESP_OFFLOAD=y
+CONFIG_INET_ESP=y
+CONFIG_INET_ESP_OFFLOAD=y
CONFIG_IO_URING=y
CONFIG_IPV6=y
CONFIG_IPV6_GRE=y
+CONFIG_IPV6_SIT=y
+CONFIG_IPV6_TUNNEL=y
CONFIG_NET_CLS_ACT=y
CONFIG_NET_CLS_BPF=y
+CONFIG_NET_DEVMEM=y
CONFIG_NET_IPGRE=y
CONFIG_NET_IPGRE_DEMUX=y
+CONFIG_NET_IPIP=y
CONFIG_NETKIT=y
CONFIG_NET_SCH_INGRESS=y
+CONFIG_SYNC_FILE=y
CONFIG_UDMABUF=y
+CONFIG_USER_NS=y
CONFIG_VXLAN=y
+CONFIG_XFRM_USER=y
diff --git a/tools/testing/selftests/drivers/net/hw/csum.py b/tools/testing/selftests/drivers/net/hw/csum.py
index 3e3a89a34afe..0e99198f8d39 100755
--- a/tools/testing/selftests/drivers/net/hw/csum.py
+++ b/tools/testing/selftests/drivers/net/hw/csum.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-"""Run the tools/testing/selftests/net/csum testsuite."""
+"""Run the tools/testing/selftests/net/lib/csum testsuite."""
from os import path
diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py
new file mode 100755
index 000000000000..4416f024cb76
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py
@@ -0,0 +1,296 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Devlink Rate Cross-eswitch Scheduling Test Suite
+==================================================
+
+Control-plane tests for cross-eswitch TX scheduling via devlink-rate.
+Validates that VFs from different PFs on the same chip can share
+rate groups using the cross-device parent-dev attribute.
+
+Preconditions:
+- NETIF points to a bond device with exactly two interfaces.
+- the interfaces must be two PFs from different devices sharing the same chip.
+- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG:
+ - devlink dev eswitch set $DEV1 mode switchdev
+ - devlink dev eswitch set $DEV2 mode switchdev
+ - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime
+ - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime
+- test cases will be skipped if:
+ - the number of interfaces in the bond device is != 2.
+ - the kernel doesn't support devlink rates.
+ - the devlink API doesn't support cross-device parents (ENODEV).
+ - cross-esw rate scheduling returns EOPNOTSUPP.
+"""
+
+import errno
+import glob
+import os
+import time
+
+from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit
+from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import NetDrvEnv, DevlinkFamily
+from lib.py import NlError
+from lib.py import cmd, defer, ip, tool
+
+
+# --- Discovery and setup ---
+
+
+def get_bond_slaves(bond_ifname):
+ """Returns sorted list of slave netdev names for a bond."""
+ pattern = f"/sys/class/net/{bond_ifname}/lower_*"
+ lowers = glob.glob(pattern)
+ if not lowers:
+ raise KsftSkipEx(f"No bond slaves for {bond_ifname}")
+ slaves = []
+ for path in sorted(lowers):
+ name = os.path.basename(path)
+ if name.startswith("lower_"):
+ name = name[len("lower_"):]
+ slaves.append(name)
+ return slaves
+
+
+def discover_pfs(cfg):
+ """Discovers both PFs from bond slaves."""
+ slaves = get_bond_slaves(cfg.ifname)
+ if len(slaves) != 2:
+ raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}")
+
+ pf0, pf1 = slaves[0], slaves[1]
+ ksft_pr(f"PF0: {pf0} PF1: {pf1}")
+ return pf0, pf1
+
+
+def get_pci_addr(ifname):
+ """Resolves PCI address for a network interface."""
+ return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device"))
+
+
+def get_vf_port_index(pf_pci):
+ """Finds devlink port-index for vf0 under pf_pci."""
+ ports = tool("devlink", "port show", json=True)["port"]
+ for port_name, props in ports.items():
+ if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0:
+ return int(port_name.split("/")[-1])
+ raise KsftSkipEx(f"VF port not found for {pf_pci}")
+
+
+def cleanup_esw(pf):
+ """Removes VFs if created by tests."""
+ cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False)
+
+
+def setup_esw(pf):
+ """Creates 1 VF on 'pf'."""
+ path = f"/sys/class/net/{pf}/device/sriov_numvfs"
+ cmd(f"echo 0 > {path}", shell=True)
+ cmd(f"echo 1 > {path}", shell=True)
+ defer(cleanup_esw, pf)
+ time.sleep(2)
+
+ vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net"
+ entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else []
+ if not entries:
+ raise KsftSkipEx(f"VF not found for {pf}")
+ ip(f"link set dev {entries[0]} up")
+
+ pf_pci = get_pci_addr(pf)
+ vf_idx = get_vf_port_index(pf_pci)
+ ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})")
+ return pf_pci, vf_idx
+
+
+# --- Rate operation helpers ---
+
+
+def rate_new(devnl, dev_pci, node_name, **kwargs):
+ """Creates rate node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ params.update(kwargs)
+ try:
+ devnl.rate_new(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_new not supported") from e
+ raise KsftFailEx("rate_new failed") from e
+
+
+def rate_get(devnl, dev_pci, node_name):
+ """Gets rate node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ return devnl.rate_get(params)
+
+
+def rate_get_leaf(devnl, dev_pci, port_index):
+ """Gets rate leaf (VF)."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ }
+ return devnl.rate_get(params)
+
+
+def rate_del(devnl, dev_pci, node_name):
+ """Deletes rate node."""
+ devnl.rate_del({
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ })
+
+
+def rate_set_leaf(devnl, dev_pci, port_index, **kwargs):
+ """Sets rate attributes on a leaf (VF)."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ }
+ params.update(kwargs)
+ try:
+ devnl.rate_set(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_set not supported") from e
+ raise KsftFailEx("rate_set failed") from e
+
+
+def rate_set_leaf_parent(devnl, dev_pci, port_index,
+ parent_name, parent_dev_pci=None):
+ """Sets a leaf's parent, optionally cross-esw."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ "rate-parent-node-name": parent_name,
+ }
+ if parent_dev_pci:
+ params["parent-dev"] = {
+ "bus-name": "pci",
+ "dev-name": parent_dev_pci,
+ }
+ try:
+ devnl.rate_set(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_set not supported") from e
+ if parent_dev_pci and e.error == errno.ENODEV:
+ raise KsftSkipEx("Cross-esw scheduling not supported") from e
+ raise KsftFailEx("rate_set failed") from e
+
+
+def rate_clear_leaf_parent(devnl, dev_pci, port_index):
+ """Clears a leaf's parent."""
+ rate_set_leaf_parent(devnl, dev_pci, port_index, "")
+
+
+def rate_set_node(devnl, dev_pci, node_name, **kwargs):
+ """Sets rate attributes on a node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ params.update(kwargs)
+ devnl.rate_set(params)
+
+
+# --- Test cases ---
+
+
+def test_same_esw_parent(cfg):
+ """Assigns PF0's VF to PF0's group (same esw baseline)."""
+ pf0, _ = discover_pfs(cfg)
+ pf0_pci, vf0_idx = setup_esw(pf0)
+
+ rate_new(cfg.devnl, pf0_pci, "group0")
+ defer(rate_del, cfg.devnl, pf0_pci, "group0")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0")
+ defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
+
+ ksft_pr("Same-esw parent assignment succeeded")
+
+
+def test_cross_esw_parent(cfg):
+ """Sets cross-esw parent, then clear it."""
+ pf0, pf1 = discover_pfs(cfg)
+ pf0_pci, _ = setup_esw(pf0)
+ pf1_pci, vf1_idx = setup_esw(pf1)
+
+ rate_new(cfg.devnl, pf0_pci, "group1")
+ defer(rate_del, cfg.devnl, pf0_pci, "group1")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
+ "group1", parent_dev_pci=pf0_pci)
+ defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
+
+ ksft_pr("Cross-esw parent set and clear succeeded")
+
+
+def test_tx_rates_on_cross_esw(cfg):
+ """Sets tx_max on group and tx_share on leaves in a cross-esw setup."""
+ pf0, pf1 = discover_pfs(cfg)
+ pf0_pci, vf0_idx = setup_esw(pf0)
+ pf1_pci, vf1_idx = setup_esw(pf1)
+
+ rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000})
+ defer(rate_del, cfg.devnl, pf0_pci, "group2")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
+ "group2", parent_dev_pci=pf0_pci)
+ defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
+ ksft_pr("set parent cross-esw succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2")
+ defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
+ ksft_pr("set parent same esw succeeded")
+
+ rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000})
+ rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx)
+ ksft_eq(rate["rate-tx-share"], 1000000)
+ rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000})
+ rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx)
+ ksft_eq(rate["rate-tx-share"], 2000000)
+ rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000})
+ rate = rate_get(cfg.devnl, pf0_pci, "group2")
+ ksft_eq(rate["rate-tx-max"], 250000000)
+
+ ksft_pr("tx_max and tx_share set on cross-esw group")
+
+
+def main() -> None:
+ """Main function."""
+
+ with NetDrvEnv(__file__, nsim_test=False) as cfg:
+ cfg.devnl = DevlinkFamily()
+
+ ksft_run(
+ cases=[
+ test_same_esw_parent,
+ test_cross_esw_parent,
+ test_tx_rates_on_cross_esw,
+ ],
+ args=(cfg,),
+ )
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index ee863e90d1e0..82c11ffc4add 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -2,91 +2,47 @@
# SPDX-License-Identifier: GPL-2.0
from os import path
-from lib.py import ksft_run, ksft_exit
-from lib.py import ksft_eq, KsftSkipEx
+from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
+ run_rx_large_niov)
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
from lib.py import NetDrvEpEnv
-from lib.py import bkg, cmd, rand_port, wait_port_listen
-from lib.py import ksft_disruptive
-
-
-def require_devmem(cfg):
- if not hasattr(cfg, "_devmem_probed"):
- probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
- cfg._devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
- cfg._devmem_probed = True
-
- if not cfg._devmem_supported:
- raise KsftSkipEx("Test requires devmem support")
@ksft_disruptive
def check_rx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- socat = f"socat -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},bind={cfg.remote_baddr}:{port}"
- listen_cmd = f"{cfg.bin_local} -l -f {cfg.ifname} -s {cfg.addr} -p {port} -c {cfg.remote_addr} -v 7"
-
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | \
- head -c 1K | {socat}", host=cfg.remote, shell=True)
-
- ksft_eq(ncdevmem.ret, 0)
+ """Run the devmem RX test."""
+ run_rx(cfg)
@ksft_disruptive
def check_tx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port}", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ """Run the devmem TX test."""
+ run_tx(cfg)
@ksft_disruptive
def check_tx_chunks(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port} -z 3", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ """Run the devmem TX chunking test."""
+ run_tx_chunks(cfg)
def check_rx_hds(cfg) -> None:
- """Test HDS splitting across payload sizes."""
- require_devmem(cfg)
-
- for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
- port = rand_port()
- listen_cmd = f"{cfg.bin_local} -L -l -f {cfg.ifname} -s {cfg.addr} -p {port}"
+ """Run the HDS test."""
+ run_rx_hds(cfg)
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | " +
- f"socat -b {size} -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},nodelay",
- host=cfg.remote, shell=True)
- ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
+def check_rx_large_niov(cfg) -> None:
+ """Run the devmem RX test with rx-page-size = 16 KiB."""
+ run_rx_large_niov(cfg)
def main() -> None:
+ """Run the devmem test cases."""
with NetDrvEpEnv(__file__) as cfg:
- cfg.bin_local = path.abspath(path.dirname(__file__) + "/ncdevmem")
- cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
-
- ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds],
- args=(cfg, ))
+ setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
+ ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds,
+ check_rx_large_niov],
+ args=(cfg,))
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
new file mode 100644
index 000000000000..3554954a6691
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
@@ -0,0 +1,316 @@
+# SPDX-License-Identifier: GPL-2.0
+# pylint: disable=invalid-name,too-many-arguments
+"""Shared helpers for devmem TCP selftests."""
+
+import os
+import re
+
+from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
+ ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily,
+ NetdevFamily)
+
+
+RX_PAGE_SIZE_DEFAULT = 0
+RX_PAGE_SIZE_16K = 16384
+
+PROBE_RX_PAGE_SIZES = (RX_PAGE_SIZE_DEFAULT, RX_PAGE_SIZE_16K)
+
+NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages"
+
+
+def _is_aligned(value, alignment):
+ """Equivalent of the kernel IS_ALIGNED(value, alignment).
+
+ alignment must be a power of two.
+ """
+ return (value & (alignment - 1)) == 0
+
+
+def _restore_nr_hugepages(nr_hugepages):
+ with open(NR_HUGEPAGES_FILE, 'w', encoding='utf-8') as f:
+ f.write(str(nr_hugepages))
+
+
+def _reserve_hugepages(want=64):
+ """Raise nr_hugepages to @want and arrange for it to be restored."""
+ with open(NR_HUGEPAGES_FILE, 'r+', encoding='utf-8') as f:
+ nr_hugepages = int(f.read().strip())
+ if nr_hugepages >= want:
+ return
+ f.seek(0)
+ f.write(str(want))
+ defer(_restore_nr_hugepages, nr_hugepages)
+
+
+def _probe_devmem(cfg, rx_page_size):
+ """Return True if ncdevmem can bind cfg.ifname at @rx_page_size."""
+ probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
+ if rx_page_size != RX_PAGE_SIZE_DEFAULT:
+ probe_command += f" -b {rx_page_size}"
+ return cmd(probe_command, fail=False, shell=True).ret == 0
+
+
+def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT):
+ """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
+ if rx_page_size not in PROBE_RX_PAGE_SIZES:
+ raise RuntimeError(
+ f"rx-page-size={rx_page_size} is missing from "
+ f"PROBE_RX_PAGE_SIZES, so it was never probed.")
+
+ if not hasattr(cfg, "devmem_supported"):
+ _reserve_hugepages()
+ # Probe every size upfront: in nk tests a leased queue may land in
+ # ncdevmem's queue range and cause the probe to fail.
+ cfg.devmem_supported = {size: _probe_devmem(cfg, size)
+ for size in PROBE_RX_PAGE_SIZES}
+
+ if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]:
+ raise KsftSkipEx("Test requires devmem support")
+
+ if rx_page_size != RX_PAGE_SIZE_DEFAULT:
+ page_size = os.sysconf("SC_PAGE_SIZE")
+ if not _is_aligned(rx_page_size, page_size):
+ raise KsftSkipEx(
+ f"rx-page-size={rx_page_size} is invalid for this platform "
+ f"(must be a multiple of PAGE_SIZE={page_size})")
+
+ if not cfg.devmem_supported[rx_page_size]:
+ raise KsftSkipEx(
+ f"Test requires devmem rx-page-size={rx_page_size} support")
+
+
+def configure_nic(cfg):
+ """Channels, rings, RSS, queue lease for netkit devmem."""
+ if not hasattr(cfg, "devmem_supported"):
+ raise RuntimeError(
+ "require_devmem() must be called before configure_nic(), which "
+ "may lease a queue away and make later probes fail.")
+
+ if not hasattr(cfg, 'netns'):
+ return
+
+ cfg.require_ipver('6')
+ ethnl = EthtoolFamily()
+
+ channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+ channels = channels['combined-count']
+ if channels < 2:
+ raise KsftSkipEx(
+ 'Test requires NETIF with at least 2 combined channels'
+ )
+
+ rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+ orig_rx_rings = rings['rx']
+ orig_hds_thresh = rings.get('hds-thresh', 0)
+
+ ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'enabled',
+ 'hds-thresh': 0,
+ 'rx': min(64, orig_rx_rings)})
+ defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'unknown',
+ 'hds-thresh': orig_hds_thresh,
+ 'rx': orig_rx_rings})
+
+ cfg.src_queue = channels - 1
+ ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ if not hasattr(cfg, 'nk_queue'):
+ with NetNSEnter(str(cfg.netns)):
+ netdevnl = NetdevFamily()
+ lease_result = netdevnl.queue_create({
+ "ifindex": cfg.nk_guest_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": cfg.ifindex,
+ "queue": {"id": cfg.src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ })
+ cfg.nk_queue = lease_result['id']
+
+
+def set_flow_rule(cfg, port):
+ """Install a flow rule steering to src_queue and return the flow rule ID."""
+ output = ethtool(
+ f"-N {cfg.ifname} flow-type tcp6 dst-port {port}"
+ f" action {cfg.src_queue}"
+ ).stdout
+ return int(re.search(r'ID (\d+)', output).group(1))
+
+
+def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
+ rx_page_size=RX_PAGE_SIZE_DEFAULT):
+ """Build the ncdevmem RX listener command."""
+ if hasattr(cfg, 'netns'):
+ flow_rule_id = set_flow_rule(cfg, port)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+ ifname = cfg.nk_guest_ifname
+ addr = cfg.nk_guest_ipv6
+ extras = [f"-t {cfg.nk_queue}", "-q 1", "-n"]
+ else:
+ ifname = cfg.ifname
+ addr = cfg.addr
+ extras = []
+ if flow_steer:
+ extras.append(f"-c {cfg.remote_addr}")
+
+ if verify:
+ extras.append("-v 7")
+ if fail_on_linear:
+ extras.append("-L")
+ if rx_page_size != RX_PAGE_SIZE_DEFAULT:
+ extras.append(f"-b {rx_page_size}")
+
+ parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}",
+ f"-p {port}", *extras]
+ return " ".join(parts)
+
+
+def ncdevmem_tx(cfg, port, chunk_size=0):
+ """Build the ncdevmem TX send command."""
+ if hasattr(cfg, 'netns'):
+ ifname = cfg.nk_guest_ifname
+ addr = cfg.remote_addr_v['6']
+ extras = ["-t 0", "-q 1", "-n"]
+ else:
+ ifname = cfg.ifname
+ addr = cfg.remote_addr
+ extras = []
+
+ if chunk_size:
+ extras.append(f"-z {chunk_size}")
+
+ parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}",
+ f"-p {port}", *extras]
+ return " ".join(parts)
+
+
+def socat_send(cfg, port, buf_size=0):
+ """Socat command for sending to the devmem listener.
+
+ When buf_size > 0, force one TCP segment per write of exactly that size by
+ setting socat's buffer (-b) and disabling Nagle (TCP_NODELAY).
+ """
+ proto = f"TCP{cfg.addr_ipver}"
+
+ if hasattr(cfg, 'netns'):
+ addr = f"[{cfg.nk_guest_ipv6}]"
+ else:
+ addr = cfg.baddr
+
+ suffix = f",bind={cfg.remote_baddr}:{port}"
+
+ buf = ""
+ if buf_size:
+ buf = f"-b {buf_size}"
+ suffix += ",nodelay"
+
+ return f"socat {buf} -u - {proto}:{addr}:{port}{suffix}"
+
+
+def socat_listen(cfg, port):
+ """Socat listen command for TX tests."""
+ return f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
+
+
+def setup_test(cfg, bin_local):
+ """Stash the local ncdevmem path on cfg and deploy it to the remote."""
+ cfg.bin_local = bin_local
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+
+def run_rx(cfg):
+ """Run the devmem RX test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ port = rand_port()
+ socat = socat_send(cfg, port)
+ data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K"
+ f" | {socat}")
+ netns = getattr(cfg, "netns", None)
+
+ listen_cmd = ncdevmem_rx(cfg, port, flow_steer=not hasattr(cfg, 'netns'))
+ with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=netns)
+ cmd(data_pipe, host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0)
+
+
+def run_tx(cfg):
+ """Run the devmem TX test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx_cmd = ncdevmem_tx(cfg, port)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_tx_chunks(cfg):
+ """Run the devmem TX chunking test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx_cmd = ncdevmem_tx(cfg, port, chunk_size=3)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_rx_large_niov(cfg):
+ """Run the devmem RX test with a large niov (rx-page-size > PAGE_SIZE).
+
+ Sweep payload sizes that straddle the niov boundary: below, equal to,
+ and above rx_page_size, to exercise sub-niov, exact-niov, and multi-niov
+ RX paths.
+ """
+ require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_16K)
+ _reserve_hugepages()
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+
+ for size in [1024, 4096, 8192, 16384, 32768, 65536]:
+ port = rand_port()
+ socat = socat_send(cfg, port)
+ listen_cmd = ncdevmem_rx(cfg, port,
+ flow_steer=not netns,
+ rx_page_size=RX_PAGE_SIZE_16K)
+ data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | "
+ f"head -c {size} | {socat}")
+ with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=netns)
+ cmd(data_pipe, host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0,
+ f"large-niov failed for payload size {size}")
+
+
+def run_rx_hds(cfg):
+ """Run the HDS test by running devmem RX across a segment size sweep."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+
+ for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
+ port = rand_port()
+
+ listen_cmd = ncdevmem_rx(cfg, port, verify=False,
+ fail_on_linear=True)
+ socat = socat_send(cfg, port, buf_size=size)
+
+ with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=netns)
+ cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | "
+ f"{socat}", host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh
index 2ec19edddfaa..a074834cbe59 100755
--- a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh
+++ b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh
@@ -65,6 +65,8 @@ bucket_test()
run_on "$iface" \
"$MZ" "$iface" -q -c "$num_tx" -p "$len" -a own -b bcast -d 10us
+ hw_stats_settle "$iface"
+
after=$(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \
jq -r ".[0].rmon[\"${set}-pktsNtoM\"][$bucket].val")
diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh
index c085d2a4c989..09f8128c51f3 100755
--- a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh
+++ b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh
@@ -43,10 +43,12 @@ traffic_test()
done
# shellcheck disable=SC2086 # needs split options
- run_on "$iface" "$MZ" "$iface" -q -c "$num_tx" $pkt_format
+ run_on "$iface" "$MZ" "$iface" -q -d 10usec -c "$num_tx" $pkt_format
# shellcheck disable=SC2086 # needs split options
- run_on "$neigh" "$MZ" "$neigh" -q -c "$num_rx" $pkt_format
+ run_on "$neigh" "$MZ" "$neigh" -q -d 10usec -c "$num_rx" $pkt_format
+
+ hw_stats_settle "$int"
for i in "${!counters[@]}"; do
read -r int grp cnt target exact_check xfail_message \
diff --git a/tools/testing/selftests/drivers/net/hw/gro_hw.py b/tools/testing/selftests/drivers/net/hw/gro_hw.py
index 10e08b22ee0e..70e76e3888bd 100755
--- a/tools/testing/selftests/drivers/net/hw/gro_hw.py
+++ b/tools/testing/selftests/drivers/net/hw/gro_hw.py
@@ -51,11 +51,21 @@ def _resolve_dmac(cfg, ipver):
return getattr(cfg, attr)
+def _require_ntuple(cfg):
+ features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ if not features["ntuple-filters"]["active"]:
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
+
def _setup_isolated_queue(cfg):
"""Set up an isolated queue for testing using ntuple filter.
Remove queue 1 from the default RSS context and steer test traffic to it.
"""
+ _require_ntuple(cfg)
test_queue = 1
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
index 240d13dbc54e..f6a8fc5fac24 100644
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
@@ -351,9 +351,6 @@ static void run_server(void)
if (ret < 0)
error(1, 0, "bind()");
- if (listen(fd, 1024) < 0)
- error(1, 0, "listen()");
-
flags |= IORING_SETUP_COOP_TASKRUN;
flags |= IORING_SETUP_SINGLE_ISSUER;
flags |= IORING_SETUP_DEFER_TASKRUN;
@@ -366,6 +363,9 @@ static void run_server(void)
if (cfg_dry_run)
return;
+ if (listen(fd, 1024) < 0)
+ error(1, 0, "listen()");
+
add_accept(&ring, fd);
tstop = gettimeofday_ms() + 5000;
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
index e81724cb5542..b7a225fe4bea 100755
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-2.0
import re
+import resource
import time
from os import path
from lib.py import ksft_run, ksft_exit, KsftSkipEx, ksft_variants, KsftNamedVariant
@@ -41,6 +42,23 @@ def set_flow_rule_rss(cfg, rss_ctx_id):
return int(values)
+def check_iou_rx_buf_len(cfg, expected_rx_buf_len):
+ """Check the io-uring memory provider exposes the expected rx_buf_len."""
+ q = cfg.netnl.queue_get({'ifindex': cfg.ifindex, 'type': 'rx', 'id': cfg.target})
+ napi_id = q['napi-id']
+ pools = cfg.netnl.page_pool_get({}, dump=True)
+ pools = [p for p in pools if p.get('napi-id') == napi_id
+ and 'io-uring' in p]
+ if len(pools) != 1:
+ raise Exception(f"Expected 1 io-uring page pool, found {len(pools)}")
+ rx_buf_len = pools[0]['io-uring'].get('rx-buf-len')
+ if rx_buf_len is None:
+ raise KsftSkipEx("io-uring 'rx-buf-len' attribute not supported")
+ if rx_buf_len != expected_rx_buf_len:
+ raise Exception(f'Expected io-uring rx-buf-len {expected_rx_buf_len}, '
+ f'got {rx_buf_len}')
+
+
def single(cfg):
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
channels = channels['combined-count']
@@ -100,12 +118,22 @@ def rss(cfg):
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+def _require_ntuple(cfg):
+ features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ if not features["ntuple-filters"]["active"]:
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
+
@ksft_variants([
KsftNamedVariant("single", single),
KsftNamedVariant("rss", rss),
])
def test_zcrx(cfg, setup) -> None:
cfg.require_ipver('6')
+ _require_ntuple(cfg)
setup(cfg)
rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target}"
@@ -121,6 +149,7 @@ def test_zcrx(cfg, setup) -> None:
])
def test_zcrx_oneshot(cfg, setup) -> None:
cfg.require_ipver('6')
+ _require_ntuple(cfg)
setup(cfg)
rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -o 4"
@@ -134,6 +163,7 @@ def test_zcrx_large_chunks(cfg) -> None:
"""Test zcrx with large buffer chunks."""
cfg.require_ipver('6')
+ _require_ntuple(cfg)
hp_file = "/proc/sys/vm/nr_hugepages"
with open(hp_file, 'r+', encoding='utf-8') as f:
@@ -144,7 +174,10 @@ def test_zcrx_large_chunks(cfg) -> None:
defer(lambda: open(hp_file, 'w', encoding='utf-8').write(str(nr_hugepages)))
single(cfg)
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -x 2"
+ page_size = resource.getpagesize()
+ nr_pages = 2
+ rx_buf_len = nr_pages * page_size
+ rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -x {nr_pages}"
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
probe = cmd(rx_cmd + " -d", fail=False)
@@ -154,6 +187,9 @@ def test_zcrx_large_chunks(cfg) -> None:
mp_clear_wait(cfg)
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(cfg.port, proto="tcp")
+
+ check_iou_rx_buf_len(cfg, rx_buf_len)
+
cmd(tx_cmd, host=cfg.remote)
diff --git a/tools/testing/selftests/drivers/net/hw/ipsec_vxlan.py b/tools/testing/selftests/drivers/net/hw/ipsec_vxlan.py
new file mode 100755
index 000000000000..0740a4d85240
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/ipsec_vxlan.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Traffic test for VXLAN + IPsec crypto-offload."""
+
+import os
+
+from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge
+from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx
+from lib.py import CmdExitFailure, NetDrvEpEnv, cmd, defer, ethtool, ip
+from lib.py import Iperf3Runner
+
+# Inner tunnel addresses - TEST-NET-2 (RFC 5737) / doc prefix (RFC 3849)
+INNER_V4_LOCAL = "198.51.100.1"
+INNER_V4_REMOTE = "198.51.100.2"
+INNER_V6_LOCAL = "2001:db8:100::1"
+INNER_V6_REMOTE = "2001:db8:100::2"
+
+# ESP parameters
+SPI_OUT = "0x1000"
+SPI_IN = "0x1001"
+# 128-bit key + 32-bit salt = 20 bytes hex, 128-bit ICV
+ESP_AEAD = "aead 'rfc4106(gcm(aes))' 0x" + "01" * 20 + " 128"
+
+
+def xfrm(args, host=None):
+ """Runs 'ip xfrm' via shell to preserve parentheses in algo names."""
+ cmd(f"ip xfrm {args}", shell=True, host=host)
+
+
+def check_xfrm_offload_support():
+ """Skips if iproute2 lacks xfrm offload support."""
+ out = cmd("ip xfrm state help", fail=False)
+ if "offload" not in out.stdout + out.stderr:
+ raise KsftSkipEx("iproute2 too old, missing xfrm offload")
+
+
+def check_esp_hw_offload(cfg):
+ """Skips if device lacks esp-hw-offload support."""
+ check_xfrm_offload_support()
+ try:
+ feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ except (CmdExitFailure, IndexError) as e:
+ raise KsftSkipEx(f"can't query features: {e}") from e
+ if not feat.get("esp-hw-offload", {}).get("active"):
+ raise KsftSkipEx("Device does not support esp-hw-offload")
+
+
+def get_tx_drops(cfg):
+ """Returns TX dropped counter from the physical device."""
+ stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0]
+ return stats["stats64"]["tx"]["dropped"]
+
+
+def setup_vxlan_ipsec(cfg, outer_ipver, inner_ipver):
+ """Sets up VXLAN tunnel with IPsec transport-mode crypto-offload."""
+ vxlan_name = f"vx{os.getpid()}"
+ local_addr = cfg.addr_v[outer_ipver]
+ remote_addr = cfg.remote_addr_v[outer_ipver]
+
+ if inner_ipver == "4":
+ inner_local = f"{INNER_V4_LOCAL}/24"
+ inner_remote = f"{INNER_V4_REMOTE}/24"
+ addr_extra = ""
+ else:
+ inner_local = f"{INNER_V6_LOCAL}/64"
+ inner_remote = f"{INNER_V6_REMOTE}/64"
+ addr_extra = " nodad"
+
+ if outer_ipver == "6":
+ vxlan_opts = "udp6zerocsumtx udp6zerocsumrx"
+ else:
+ vxlan_opts = "noudpcsum"
+
+ # VXLAN tunnel - local side
+ ip(f"link add {vxlan_name} type vxlan id 100 dstport 4789 {vxlan_opts} "
+ f"local {local_addr} remote {remote_addr} dev {cfg.ifname}")
+ defer(ip, f"link del {vxlan_name}")
+ ip(f"addr add {inner_local} dev {vxlan_name}{addr_extra}")
+ ip(f"link set {vxlan_name} up")
+
+ # VXLAN tunnel - remote side
+ ip(f"link add {vxlan_name} type vxlan id 100 dstport 4789 {vxlan_opts} "
+ f"local {remote_addr} remote {local_addr} dev {cfg.remote_ifname}",
+ host=cfg.remote)
+ defer(ip, f"link del {vxlan_name}", host=cfg.remote)
+ ip(f"addr add {inner_remote} dev {vxlan_name}{addr_extra}",
+ host=cfg.remote)
+ ip(f"link set {vxlan_name} up", host=cfg.remote)
+
+ # xfrm state - local outbound SA
+ xfrm(f"state add src {local_addr} dst {remote_addr} "
+ f"proto esp spi {SPI_OUT} "
+ f"{ESP_AEAD} "
+ f"mode transport offload crypto dev {cfg.ifname} dir out")
+ defer(xfrm, f"state del src {local_addr} dst {remote_addr} "
+ f"proto esp spi {SPI_OUT}")
+
+ # xfrm state - local inbound SA
+ xfrm(f"state add src {remote_addr} dst {local_addr} "
+ f"proto esp spi {SPI_IN} "
+ f"{ESP_AEAD} "
+ f"mode transport offload crypto dev {cfg.ifname} dir in")
+ defer(xfrm, f"state del src {remote_addr} dst {local_addr} "
+ f"proto esp spi {SPI_IN}")
+
+ # xfrm state - remote outbound SA (mirror, software crypto)
+ xfrm(f"state add src {remote_addr} dst {local_addr} "
+ f"proto esp spi {SPI_IN} "
+ f"{ESP_AEAD} "
+ f"mode transport",
+ host=cfg.remote)
+ defer(xfrm, f"state del src {remote_addr} dst {local_addr} "
+ f"proto esp spi {SPI_IN}", host=cfg.remote)
+
+ # xfrm state - remote inbound SA (mirror, software crypto)
+ xfrm(f"state add src {local_addr} dst {remote_addr} "
+ f"proto esp spi {SPI_OUT} "
+ f"{ESP_AEAD} "
+ f"mode transport",
+ host=cfg.remote)
+ defer(xfrm, f"state del src {local_addr} dst {remote_addr} "
+ f"proto esp spi {SPI_OUT}", host=cfg.remote)
+
+ # xfrm policy - local out
+ xfrm(f"policy add src {local_addr} dst {remote_addr} "
+ f"proto udp dport 4789 dir out "
+ f"tmpl src {local_addr} dst {remote_addr} proto esp mode transport")
+ defer(xfrm, f"policy del src {local_addr} dst {remote_addr} "
+ f"proto udp dport 4789 dir out")
+
+ # xfrm policy - local in
+ xfrm(f"policy add src {remote_addr} dst {local_addr} "
+ f"proto udp dport 4789 dir in "
+ f"tmpl src {remote_addr} dst {local_addr} proto esp mode transport")
+ defer(xfrm, f"policy del src {remote_addr} dst {local_addr} "
+ f"proto udp dport 4789 dir in")
+
+ # xfrm policy - remote out
+ xfrm(f"policy add src {remote_addr} dst {local_addr} "
+ f"proto udp dport 4789 dir out "
+ f"tmpl src {remote_addr} dst {local_addr} proto esp mode transport",
+ host=cfg.remote)
+ defer(xfrm, f"policy del src {remote_addr} dst {local_addr} "
+ f"proto udp dport 4789 dir out", host=cfg.remote)
+
+ # xfrm policy - remote in
+ xfrm(f"policy add src {local_addr} dst {remote_addr} "
+ f"proto udp dport 4789 dir in "
+ f"tmpl src {local_addr} dst {remote_addr} proto esp mode transport",
+ host=cfg.remote)
+ defer(xfrm, f"policy del src {local_addr} dst {remote_addr} "
+ f"proto udp dport 4789 dir in", host=cfg.remote)
+
+
+def _vxlan_ipsec_variants():
+ """Generates outer/inner IP version variants."""
+ for outer in ["4", "6"]:
+ for inner in ["4", "6"]:
+ yield KsftNamedVariant(f"outer_v{outer}_inner_v{inner}", outer, inner)
+
+
+@ksft_variants(_vxlan_ipsec_variants())
+def test_vxlan_ipsec_crypto_offload(cfg, outer_ipver, inner_ipver):
+ """Tests VXLAN+IPsec crypto-offload has no TX drops."""
+ cfg.require_ipver(outer_ipver)
+ check_esp_hw_offload(cfg)
+
+ setup_vxlan_ipsec(cfg, outer_ipver, inner_ipver)
+
+ if inner_ipver == "4":
+ inner_local = INNER_V4_LOCAL
+ inner_remote = INNER_V4_REMOTE
+ ping = "ping"
+ else:
+ inner_local = INNER_V6_LOCAL
+ inner_remote = INNER_V6_REMOTE
+ ping = "ping -6"
+
+ cmd(f"{ping} -c 1 -W 2 {inner_remote}")
+
+ drops_before = get_tx_drops(cfg)
+
+ runner = Iperf3Runner(cfg, server_ip=inner_local,
+ client_ip=inner_remote)
+ bw_gbps = runner.measure_bandwidth(reverse=True)
+
+ cfg.wait_hw_stats_settle()
+ drops_after = get_tx_drops(cfg)
+
+ ksft_eq(drops_after - drops_before, 0,
+ comment="TX drops during VXLAN+IPsec")
+ ksft_ge(bw_gbps, 0.1,
+ comment="Minimum 100Mbps over VXLAN+IPsec")
+
+
+def main():
+ """Runs VXLAN+IPsec crypto-offload GSO selftest."""
+ with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+ ksft_run([test_vxlan_ipsec_crypto_offload], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
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 84a4dab6c649..8a58cb17cc06 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
@@ -18,7 +18,7 @@ try:
sys.path.append(KSFT_DIR.as_posix())
# Import one by one to avoid pylint false positives
- from net.lib.py import NetNS, NetNSEnter, NetdevSimDev
+ from net.lib.py import NetNS, NetNSEnter, NetdevSimDev, UserNetNS
from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \
NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
from net.lib.py import CmdExitFailure
@@ -34,7 +34,7 @@ try:
from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
- __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
+ __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", "UserNetNS",
"EthtoolFamily", "NetdevFamily", "NetshaperFamily",
"NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
"CmdExitFailure",
diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index e098d6534c3c..918e3b51f3b8 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -40,6 +40,7 @@
#include <linux/uio.h>
#include <stdarg.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -61,6 +62,7 @@
#include <sys/time.h>
#include <linux/memfd.h>
+#include <sys/param.h>
#include <linux/dma-buf.h>
#include <linux/errqueue.h>
#include <linux/udmabuf.h>
@@ -79,6 +81,7 @@
#define PAGE_SHIFT 12
#define TEST_PREFIX "ncdevmem"
#define NUM_PAGES 16000
+#define MB(x) ((x) << 20)
#ifndef MSG_SOCK_DEVMEM
#define MSG_SOCK_DEVMEM 0x2000000
@@ -93,12 +96,14 @@ static char *port;
static size_t do_validation;
static int start_queue = -1;
static int num_queues = -1;
+static int skip_config;
static char *ifname;
static unsigned int ifindex;
static unsigned int dmabuf_id;
static uint32_t tx_dmabuf_id;
static int waittime_ms = 500;
static bool fail_on_linear;
+static uint32_t rx_page_size;
/* System state loaded by current_config_load() */
#define MAX_FLOWS 8
@@ -141,6 +146,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
{
struct udmabuf_create create;
struct memory_buffer *ctx;
+ unsigned int memfd_flags;
int ret;
ctx = malloc(sizeof(*ctx));
@@ -149,15 +155,20 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
ctx->size = size;
- ctx->devfd = open("/dev/udmabuf", O_RDWR);
+ ctx->devfd = open("/dev/udmabuf", O_RDONLY);
if (ctx->devfd < 0) {
pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");
goto err_free_ctx;
}
- ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING);
+ memfd_flags = MFD_ALLOW_SEALING;
+ if (rx_page_size > getpagesize())
+ memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB;
+
+ ctx->memfd = memfd_create("udmabuf-test", memfd_flags);
if (ctx->memfd < 0) {
- pr_err("[skip,no-memfd]");
+ pr_err("[skip,no-memfd%s]",
+ (memfd_flags & MFD_HUGETLB) ? " (need hugepages)" : "");
goto err_close_dev;
}
@@ -167,6 +178,11 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
goto err_close_memfd;
}
+ if (memfd_flags & MFD_HUGETLB) {
+ size = roundup(size, MB(2));
+ ctx->size = size;
+ }
+
ret = ftruncate(ctx->memfd, size);
if (ret == -1) {
pr_err("[FAIL,memfd-truncate]");
@@ -698,6 +714,8 @@ static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd,
netdev_bind_rx_req_set_ifindex(req, ifindex);
netdev_bind_rx_req_set_fd(req, dmabuf_fd);
__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);
+ if (rx_page_size)
+ netdev_bind_rx_req_set_rx_page_size(req, rx_page_size);
rsp = netdev_bind_rx(*ys, req);
if (!rsp) {
@@ -828,7 +846,7 @@ static struct netdev_queue_id *create_queues(void)
static int do_server(struct memory_buffer *mem)
{
- struct ethtool_rings_get_rsp *ring_config;
+ struct ethtool_rings_get_rsp *ring_config = NULL;
char ctrl_data[sizeof(int) * 20000];
size_t non_page_aligned_frags = 0;
struct sockaddr_in6 client_addr;
@@ -851,27 +869,29 @@ static int do_server(struct memory_buffer *mem)
return -1;
}
- ring_config = get_ring_config();
- if (!ring_config) {
- pr_err("Failed to get current ring configuration");
- return -1;
- }
+ if (!skip_config) {
+ ring_config = get_ring_config();
+ if (!ring_config) {
+ pr_err("Failed to get current ring configuration");
+ return -1;
+ }
- if (configure_headersplit(ring_config, 1)) {
- pr_err("Failed to enable TCP header split");
- goto err_free_ring_config;
- }
+ if (configure_headersplit(ring_config, 1)) {
+ pr_err("Failed to enable TCP header split");
+ goto err_free_ring_config;
+ }
- /* Configure RSS to divert all traffic from our devmem queues */
- if (configure_rss()) {
- pr_err("Failed to configure rss");
- goto err_reset_headersplit;
- }
+ /* Configure RSS to divert all traffic from our devmem queues */
+ if (configure_rss()) {
+ pr_err("Failed to configure rss");
+ goto err_reset_headersplit;
+ }
- /* Flow steer our devmem flows to start_queue */
- if (configure_flow_steering(&server_sin)) {
- pr_err("Failed to configure flow steering");
- goto err_reset_rss;
+ /* Flow steer our devmem flows to start_queue */
+ if (configure_flow_steering(&server_sin)) {
+ pr_err("Failed to configure flow steering");
+ goto err_reset_rss;
+ }
}
if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys)) {
@@ -1052,13 +1072,17 @@ err_free_tmp:
err_unbind:
ynl_sock_destroy(ys);
err_reset_flow_steering:
- reset_flow_steering();
+ if (!skip_config)
+ reset_flow_steering();
err_reset_rss:
- reset_rss();
+ if (!skip_config)
+ reset_rss();
err_reset_headersplit:
- restore_ring_config(ring_config);
+ if (!skip_config)
+ restore_ring_config(ring_config);
err_free_ring_config:
- ethtool_rings_get_rsp_free(ring_config);
+ if (!skip_config)
+ ethtool_rings_get_rsp_free(ring_config);
return err;
}
@@ -1404,7 +1428,7 @@ int main(int argc, char *argv[])
int is_server = 0, opt;
int ret, err = 1;
- while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:")) != -1) {
+ while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:")) != -1) {
switch (opt) {
case 'L':
fail_on_linear = true;
@@ -1436,6 +1460,22 @@ int main(int argc, char *argv[])
case 'z':
max_chunk = atoi(optarg);
break;
+ case 'n':
+ skip_config = 1;
+ break;
+ case 'b': {
+ unsigned long val;
+
+ errno = 0;
+ val = strtoul(optarg, NULL, 0);
+ if ((val == ULONG_MAX && errno == ERANGE) ||
+ val > UINT32_MAX) {
+ pr_err("invalid rx_page_size: %s", optarg);
+ return 1;
+ }
+ rx_page_size = val;
+ break;
+ }
case '?':
fprintf(stderr, "unknown option: %c\n", optopt);
break;
diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
new file mode 100755
index 000000000000..61c6f31f01e5
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Test devmem TCP with netkit."""
+
+import os
+from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
+ run_rx_large_niov)
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
+from lib.py import NetDrvContEnv
+
+
+@ksft_disruptive
+def check_nk_rx(cfg) -> None:
+ """Run the devmem RX test through netkit."""
+ run_rx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx(cfg) -> None:
+ """Run the devmem TX test through netkit."""
+ run_tx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx_chunks(cfg) -> None:
+ """Run the devmem TX chunking test through netkit."""
+ run_tx_chunks(cfg)
+
+
+def check_nk_rx_hds(cfg) -> None:
+ """Run the HDS test through netkit."""
+ run_rx_hds(cfg)
+
+
+def check_nk_rx_large_niov(cfg) -> None:
+ """Run the devmem RX large-niov test through netkit."""
+ run_rx_large_niov(cfg)
+
+
+def main() -> None:
+ """Run the netkit devmem test cases."""
+ with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
+ setup_test(cfg,
+ os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "ncdevmem"))
+ ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks,
+ check_nk_rx_hds, check_nk_rx_large_niov], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
new file mode 100644
index 000000000000..46ff494b23de
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+volatile __u32 phys_ifindex;
+
+SEC("tc/ingress")
+int nk_primary_rx_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct ethhdr *eth;
+ struct ipv6hdr *ip6h;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (eth->h_proto != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (ip6h->nexthdr == IPPROTO_ICMPV6)
+ return TC_ACT_OK;
+
+ return bpf_redirect_neigh(phys_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/drivers/net/hw/nk_qlease.py b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
index aa83dc321328..4f53034c9a50 100755
--- a/tools/testing/selftests/drivers/net/hw/nk_qlease.py
+++ b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
@@ -18,8 +18,10 @@ from lib.py import (
NetNSEnter,
EthtoolFamily,
NetdevFamily,
+ RtnlFamily,
)
from lib.py import (
+ Netlink,
bkg,
cmd,
defer,
@@ -30,10 +32,138 @@ from lib.py import (
)
from lib.py import KsftSkipEx, CmdExitFailure
+# iou-zcrx exits with 42 from setup_zcrx() when the NIC does not advertise
+# QCFG_RX_PAGE_SIZE (or otherwise rejects the requested rx_buf_len).
+SKIP_CODE = 42
-def set_flow_rule(cfg):
+
+def _restore_hugepages(count):
+ with open("/proc/sys/vm/nr_hugepages", "w", encoding="utf-8") as f:
+ f.write(str(count))
+
+
+def _mp_clear_wait(cfg, src_queue):
+ """Wait for the io_uring memory provider to clear from the leased
+ physical queue; io_uring tears it down asynchronously after the
+ process holding the ifq exits."""
+ netdevnl = NetdevFamily()
+ deadline = time.time() + 5
+ while time.time() < deadline:
+ queue_info = netdevnl.queue_get(
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
+ )
+ if "io-uring" not in queue_info:
+ return
+ time.sleep(0.1)
+ raise TimeoutError("Timed out waiting for memory provider to clear")
+
+
+def _create_netkit_pair(cfg, rxqueues=2):
+ if cfg.nk_host_ifname:
+ cmd(f"ip link del dev {cfg.nk_host_ifname}", fail=False)
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
+ cfg.detach_bpf()
+
+ all_links = ip("-d link show", json=True)
+ old_idxs = {
+ link["ifindex"]
+ for link in all_links
+ if link.get("linkinfo", {}).get("info_kind") == "netkit"
+ }
+
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": "l2",
+ "policy": "forward",
+ "peer-policy": "forward",
+ },
+ },
+ "num-rx-queues": rxqueues,
+ },
+ flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+ )
+
+ all_links = ip("-d link show", json=True)
+ nk_links = [
+ link
+ for link in all_links
+ if link.get("linkinfo", {}).get("info_kind") == "netkit"
+ and link["ifindex"] not in old_idxs
+ ]
+ if len(nk_links) != 2:
+ raise KsftSkipEx("Failed to create netkit pair")
+
+ nk_links.sort(key=lambda x: x["ifindex"])
+ cfg.nk_host_ifname = nk_links[1]["ifname"]
+ cfg.nk_guest_ifname = nk_links[0]["ifname"]
+ cfg.nk_host_ifindex = nk_links[1]["ifindex"]
+ cfg.nk_guest_ifindex = nk_links[0]["ifindex"]
+
+ ip(f"link set dev {cfg.nk_guest_ifname} netns {cfg.netns.name}")
+ ip(f"link set dev {cfg.nk_host_ifname} up")
+ ip(f"-6 addr add fe80::1/64 dev {cfg.nk_host_ifname} nodad")
+ ip(
+ f"-6 route add {cfg.nk_guest_ipv6}/128 via fe80::2 "
+ f"dev {cfg.nk_host_ifname}"
+ )
+ ip(f"link set dev {cfg.nk_guest_ifname} up", ns=cfg.netns)
+ ip(f"-6 addr add fe80::2/64 dev {cfg.nk_guest_ifname}", ns=cfg.netns)
+ ip(
+ f"-6 addr add {cfg.nk_guest_ipv6}/64 dev {cfg.nk_guest_ifname} nodad",
+ ns=cfg.netns,
+ )
+ ip(
+ f"-6 route add default via fe80::1 dev {cfg.nk_guest_ifname}",
+ ns=cfg.netns,
+ )
+
+ cfg.attach_bpf()
+
+
+def _setup_lease(cfg, rxqueues=2):
+ _create_netkit_pair(cfg, rxqueues=rxqueues)
+
+ ethnl = EthtoolFamily()
+ channels = ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})[
+ "combined-count"
+ ]
+ if channels < 2:
+ raise KsftSkipEx(
+ "Test requires NETIF with at least 2 combined channels"
+ )
+ src_queue = channels - 1
+
+ with NetNSEnter(str(cfg.netns)):
+ netdevnl = NetdevFamily()
+ bind_result = netdevnl.queue_create(
+ {
+ "ifindex": cfg.nk_guest_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": cfg.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ return src_queue, bind_result["id"]
+
+
+def _teardown_netkit(cfg):
+ if cfg.nk_host_ifname:
+ cmd(f"ip link del dev {cfg.nk_host_ifname}", fail=False)
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
+
+
+def set_flow_rule(cfg, src_queue):
output = ethtool(
- f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.src_queue}"
+ f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {src_queue}"
).stdout
values = re.search(r"ID (\d+)", output).group(1)
return int(values)
@@ -41,6 +171,8 @@ def set_flow_rule(cfg):
def test_iou_zcrx(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -65,40 +197,121 @@ def test_iou_zcrx(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
- flow_rule_id = set_flow_rule(cfg)
+ flow_rule_id = set_flow_rule(cfg, src_queue)
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840"
- with bkg(rx_cmd, exit_wait=True):
+ with bkg(rx_cmd, exit_wait=True, ns=cfg.netns):
+ wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
+ cmd(tx_cmd, host=cfg.remote)
+
+
+def test_iou_zcrx_large_buf(cfg) -> None:
+ """iou-zcrx with rx_buf_len > page size, going through a netkit-leased
+ queue. Exercises the queue rx-buf-len path via netif_mp_open_rxq()'s
+ lease redirect: the netkit ifindex is opaque to io_uring, but
+ rx_page_size is honoured by the *physical* qops because the lease
+ pointer rewrites the request from netkit onto the leased physical
+ rxq before supported_params/validate_qcfg are consulted.
+ """
+ cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
+ ethnl = EthtoolFamily()
+
+ with open("/proc/sys/vm/nr_hugepages", "r+", encoding="utf-8") as f:
+ nr_hugepages = int(f.read().strip())
+ if nr_hugepages < 64:
+ f.seek(0)
+ f.write("64")
+ defer(_restore_hugepages, nr_hugepages)
+
+ rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
+ rx_rings = rings["rx"]
+ hds_thresh = rings.get("hds-thresh", 0)
+
+ ethnl.rings_set(
+ {
+ "header": {"dev-index": cfg.ifindex},
+ "tcp-data-split": "enabled",
+ "hds-thresh": 0,
+ "rx": 64,
+ }
+ )
+ defer(
+ ethnl.rings_set,
+ {
+ "header": {"dev-index": cfg.ifindex},
+ "tcp-data-split": "unknown",
+ "hds-thresh": hds_thresh,
+ "rx": rx_rings,
+ },
+ )
+
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ flow_rule_id = set_flow_rule(cfg, src_queue)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+ # -x 2 asks iou-zcrx for rx_buf_len = 2 * page_size (8 KiB on x86_64),
+ # backed by a 2 MiB hugepage area so the chunks are physically
+ # contiguous, which is what zcrx requires for non-default rx_buf_len.
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue} -x 2"
+ )
+ tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840"
+
+ # Probe via -d (dry run): exits with SKIP_CODE if the leased physical
+ # qops doesn't advertise QCFG_RX_PAGE_SIZE (e.g. older bnxt FW/HW).
+ probe = cmd(rx_cmd + " -d", fail=False, ns=cfg.netns)
+ if probe.ret == SKIP_CODE:
+ msg = probe.stdout.strip() or "rx_buf_len not supported by leased NIC"
+ raise KsftSkipEx(msg)
+
+ # A successful dry run still registered the zcrx ifq on the leased
+ # physical queue; wait for its async teardown before the real server
+ # binds the same queue.
+ _mp_clear_wait(cfg, src_queue)
+
+ with bkg(rx_cmd, exit_wait=True, ns=cfg.netns):
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
cmd(tx_cmd, host=cfg.remote)
def test_attrs(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
netdevnl = NetdevFamily()
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
- ksft_eq(queue_info["id"], cfg.src_queue)
+ ksft_eq(queue_info["id"], src_queue)
ksft_eq(queue_info["type"], "rx")
ksft_eq(queue_info["ifindex"], cfg.ifindex)
ksft_in("lease", queue_info)
lease = queue_info["lease"]
ksft_eq(lease["ifindex"], cfg.nk_guest_ifindex)
- ksft_eq(lease["queue"]["id"], cfg.nk_queue)
+ ksft_eq(lease["queue"]["id"], nk_queue)
ksft_eq(lease["queue"]["type"], "rx")
ksft_in("netns-id", lease)
def test_attach_xdp_with_mp(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -123,18 +336,21 @@ def test_attach_xdp_with_mp(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
netdevnl = NetdevFamily()
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
- with bkg(rx_cmd):
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
+ with bkg(rx_cmd, ns=cfg.netns):
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_in("io-uring", queue_info)
@@ -144,13 +360,15 @@ def test_attach_xdp_with_mp(cfg) -> None:
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
def test_destroy(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -175,16 +393,19 @@ def test_destroy(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
- rx_proc = cmd(rx_cmd, background=True)
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
+ rx_proc = cmd(rx_cmd, background=True, ns=cfg.netns)
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
netdevnl = NetdevFamily()
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_in("io-uring", queue_info)
@@ -193,23 +414,20 @@ def test_destroy(cfg) -> None:
kill_timer = threading.Timer(1, rx_proc.proc.terminate)
kill_timer.start()
- ip(f"link del dev {cfg._nk_host_ifname}")
+ ip(f"link del dev {cfg.nk_host_ifname}")
kill_timer.join()
- cfg._nk_host_ifname = None
- cfg._nk_guest_ifname = None
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
- cmd(f"tc filter del dev {cfg.ifname} ingress pref {cfg._bpf_prog_pref}")
- cfg._tc_attached = False
-
- flow_rule_id = set_flow_rule(cfg)
+ flow_rule_id = set_flow_rule(cfg, src_queue)
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.src_queue}"
+ rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {src_queue}"
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(cfg.port, proto="tcp")
@@ -217,7 +435,7 @@ def test_destroy(cfg) -> None:
# Short delay since iou cleanup is async and takes a bit of time.
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
@@ -230,32 +448,14 @@ def main() -> None:
cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
cfg.port = rand_port()
- ethnl = EthtoolFamily()
- channels = ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})
- channels = channels["combined-count"]
- if channels < 2:
- raise KsftSkipEx("Test requires NETIF with at least 2 combined channels")
-
- cfg.src_queue = channels - 1
-
- with NetNSEnter(str(cfg.netns)):
- netdevnl = NetdevFamily()
- bind_result = netdevnl.queue_create(
- {
- "ifindex": cfg.nk_guest_ifindex,
- "type": "rx",
- "lease": {
- "ifindex": cfg.ifindex,
- "queue": {"id": cfg.src_queue, "type": "rx"},
- "netns-id": 0,
- },
- }
- )
- cfg.nk_queue = bind_result["id"]
-
- # test_destroy must be last because it destroys the netkit devices
ksft_run(
- [test_iou_zcrx, test_attrs, test_attach_xdp_with_mp, test_destroy],
+ [
+ test_iou_zcrx,
+ test_iou_zcrx_large_buf,
+ test_attrs,
+ test_attach_xdp_with_mp,
+ test_destroy,
+ ],
args=(cfg,),
)
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/ntuple.py b/tools/testing/selftests/drivers/net/hw/ntuple.py
index 232733142c02..ef4604bfa8ef 100755
--- a/tools/testing/selftests/drivers/net/hw/ntuple.py
+++ b/tools/testing/selftests/drivers/net/hw/ntuple.py
@@ -22,7 +22,10 @@ class NtupleField(Enum):
def _require_ntuple(cfg):
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
if not features["ntuple-filters"]["active"]:
- raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
def _get_rx_cnts(cfg, prev=None):
diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
index 51f4e7bc3e5d..5b25fa89c629 100755
--- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py
+++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
@@ -9,7 +9,7 @@ from lib.py import ksft_disruptive
from lib.py import ksft_run, ksft_pr, ksft_exit
from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises
from lib.py import NetDrvEpEnv
-from lib.py import EthtoolFamily, NetdevFamily
+from lib.py import EthtoolFamily, NetdevFamily, NlError
from lib.py import KsftSkipEx, KsftFailEx
from lib.py import rand_port, rand_ports
from lib.py import cmd, ethtool, ip, defer, CmdExitFailure, wait_file
@@ -57,9 +57,10 @@ def ethtool_create(cfg, act, opts):
def require_ntuple(cfg):
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
if not features["ntuple-filters"]["active"]:
- # ntuple is more of a capability than a config knob, don't bother
- # trying to enable it (until some driver actually needs it).
- raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
def require_context_cnt(cfg, need_cnt):
@@ -650,9 +651,14 @@ def test_rss_context_overlap(cfg, other_ctx=0):
ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
# Test the main context
- cnts = _get_rx_cnts(cfg)
- GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
- cnts = _get_rx_cnts(cfg, prev=cnts)
+ attempts = 3
+ for attempt in range(attempts):
+ cnts = _get_rx_cnts(cfg)
+ GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
+ cnts = _get_rx_cnts(cfg, prev=cnts)
+ if sum(cnts[:2]) >= 7000 and sum(cnts[2:4]) >= 7000:
+ break
+ ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts))
ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts))
@@ -828,6 +834,94 @@ def test_rss_default_context_rule(cfg):
'noise' : (0, 1) })
+def _set_flow_hash(cfg, fl_type, fields, context=0):
+ req = {"header": {"dev-index": cfg.ifindex},
+ "flow-hash": {fl_type: fields}}
+ if context:
+ req["context"] = context
+ cfg.ethnl.rss_set(req)
+
+
+def _get_flow_hash(cfg, fl_type, context=0):
+ req = {"header": {"dev-index": cfg.ifindex}}
+ if context:
+ req["context"] = context
+ rss = cfg.ethnl.rss_get(req)
+ return rss.get("flow-hash", {}).get(fl_type, set())
+
+
+def test_rss_context_flow_hash(cfg):
+ """
+ Validate, with traffic, that an additional RSS context honors the
+ flow-hash field selection. If the driver lacks per-context field
+ configuration ("ops->rxfh_per_ctx_fields") fall back to setting the
+ fields on the main context, which the kernel applies device-wide.
+ """
+
+ require_ntuple(cfg)
+
+ queue_cnt = len(_get_rx_cnts(cfg))
+ if queue_cnt < 6:
+ try:
+ ksft_pr(f"Increasing queue count {queue_cnt} -> 6")
+ ethtool(f"-L {cfg.ifname} combined 6")
+ defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
+ except CmdExitFailure as exc:
+ raise KsftSkipEx("Not enough queues for the test") from exc
+
+ fl_type = f"tcp{cfg.addr_ipver}"
+ if not _get_flow_hash(cfg, fl_type):
+ raise KsftSkipEx(f"Device does not report flow-hash for {fl_type}")
+
+ # Reserve queues 0/1 for main, build a new context spanning 2..5
+ ethtool(f"-X {cfg.ifname} equal 2")
+ defer(ethtool, f"-X {cfg.ifname} default")
+ ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 4")
+ defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
+
+ port = rand_port()
+ flow = f"flow-type {fl_type} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
+ ntuple = ethtool_create(cfg, "-N", flow)
+ defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
+
+ ip_only = {"ip-src", "ip-dst"}
+ ip_l4 = ip_only | {"l4-b-0-1", "l4-b-2-3"}
+
+ # Try per-context flow-hash; fall back to main context if unsupported.
+ cfg_ctx = ctx_id
+ try:
+ orig = _get_flow_hash(cfg, fl_type, context=ctx_id)
+ _set_flow_hash(cfg, fl_type, ip_only, context=ctx_id)
+ except NlError:
+ ksft_pr("Per-context flow-hash not supported, using device-wide")
+ cfg_ctx = 0
+ orig = _get_flow_hash(cfg, fl_type)
+ _set_flow_hash(cfg, fl_type, ip_only)
+ defer(_set_flow_hash, cfg, fl_type, orig, context=cfg_ctx)
+
+ def measure():
+ cnts = _get_rx_cnts(cfg)
+ GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
+ cnts = _get_rx_cnts(cfg, prev=cnts)
+ ctx_cnts = cnts[2:6]
+ directed = sum(ctx_cnts)
+ used = sum(1 for c in ctx_cnts if c > directed / 200)
+ return cnts, directed, used
+
+ # IP-only hash: iperf3 streams share src/dst IP, all should land on the
+ # same queue inside the context's range.
+ cnts, directed, used = measure()
+ ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP-only): {cnts}")
+ ksft_eq(used, 1, f"IP-only hash should use one queue in context {ctx_id}, got: {cnts}")
+
+ # IP+L4 hash: streams have distinct src ports, traffic should spread.
+ _set_flow_hash(cfg, fl_type, ip_l4, context=cfg_ctx)
+
+ cnts, directed, used = measure()
+ ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP+L4): {cnts}")
+ ksft_ge(used, 2, f"IP+L4 hash should spread across context {ctx_id} queues, got: {cnts}")
+
+
@ksft_disruptive
def test_rss_context_persist_ifupdown(cfg, pre_down=False):
"""
@@ -935,6 +1029,7 @@ def main() -> None:
test_flow_add_context_missing,
test_delete_rss_context_busy, test_rss_ntuple_addition,
test_rss_default_context_rule,
+ test_rss_context_flow_hash,
test_rss_context_persist_create_and_ifdown,
test_rss_context_persist_ifdown_and_create],
args=(cfg, ))
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py
index cd7e080e6f84..571732198b93 100755
--- a/tools/testing/selftests/drivers/net/hw/toeplitz.py
+++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py
@@ -21,6 +21,8 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
ETH_RSS_HASH_TOP = 1
# Must match RPS_MAX_CPUS in toeplitz.c
RPS_MAX_CPUS = 16
+# Cap Rx queues so IRQ pinning leaves free CPUs in the RPS_MAX_CPUS range
+QUEUE_CAP = 8
def _check_rps_and_rfs_not_configured(cfg):
@@ -48,6 +50,25 @@ def _get_cpu_for_irq(irq):
return int(data)
+def _cap_queue_count(cfg):
+ ehdr = {"header": {"dev-index": cfg.ifindex}}
+ chans = cfg.ethnl.channels_get(ehdr)
+
+ config = {}
+ restore = {}
+ for key in ("combined-count", "rx-count"):
+ cur = chans.get(key, 0)
+ if cur > QUEUE_CAP:
+ config[key] = QUEUE_CAP
+ restore[key] = cur
+
+ if not config:
+ return
+
+ cfg.ethnl.channels_set(ehdr | config)
+ defer(cfg.ethnl.channels_set, ehdr | restore)
+
+
def _get_irq_cpus(cfg):
"""
Read the list of IRQs for the device Rx queues.
@@ -177,6 +198,7 @@ def test(cfg, proto_flag, ipver, grp):
]
if grp:
+ _cap_queue_count(cfg)
_check_rps_and_rfs_not_configured(cfg)
if grp == "rss":
irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])
diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py
index bb675e3dac88..67f6c9ca9a64 100755
--- a/tools/testing/selftests/drivers/net/hw/tso.py
+++ b/tools/testing/selftests/drivers/net/hw/tso.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-"""Run the tools/testing/selftests/net/csum testsuite."""
+"""A simple test for TSO."""
import fcntl
import socket
@@ -187,28 +187,24 @@ def query_nic_features(cfg) -> None:
cfg.wanted_features.add(f["name"])
cfg.hw_features = set()
- hw_all_features_cmd = ""
for f in features["hw"]["bits"]["bit"]:
if f.get("value", False):
- feature = f["name"]
- cfg.hw_features.add(feature)
- hw_all_features_cmd += f" {feature} on"
- try:
- ethtool(f"-K {cfg.ifname} {hw_all_features_cmd}")
- except Exception as e:
- ksft_pr(f"WARNING: failure enabling all hw features: {e}")
- ksft_pr("partial gso feature detection may be impacted")
+ cfg.hw_features.add(f["name"])
# Check which features are supported via GSO partial
cfg.partial_features = set()
if 'tx-gso-partial' in cfg.hw_features:
+ seg_features = {f for f in cfg.hw_features if "segmentation" in f}
+ ethtool(f"-K {cfg.ifname} " +
+ " ".join(f"{f} on" for f in seg_features))
+
ethtool(f"-K {cfg.ifname} tx-gso-partial off")
no_partial = set()
features = cfg.ethnl.features_get({"header": {"dev-index": cfg.ifindex}})
for f in features["active"]["bits"]["bit"]:
no_partial.add(f["name"])
- cfg.partial_features = cfg.hw_features - no_partial
+ cfg.partial_features = seg_features - no_partial
ethtool(f"-K {cfg.ifname} tx-gso-partial on")
restore_wanted_features(cfg)
@@ -239,6 +235,9 @@ def main() -> None:
("vxlan_csum", "", "tx-udp_tnl-csum-segmentation", ("vxlan", "id 100 dstport 4789 udpcsum", ("4", "6"))),
("gre", "4", "tx-gre-segmentation", ("gre", "", ("4", "6"))),
("gre", "6", "tx-gre-segmentation", ("ip6gre","", ("4", "6"))),
+ ("ip", "6", "tx-ipxip6-segmentation", ("ip6tnl","mode any", ("4", "6"))),
+ ("ip", "4", "tx-ipxip4-segmentation", ("sit","", ("6", ))),
+ ("ip", "4", "tx-ipxip4-segmentation", ("ipip","", ("4", ))),
)
cases = []
diff --git a/tools/testing/selftests/drivers/net/hw/userns_devmem.py b/tools/testing/selftests/drivers/net/hw/userns_devmem.py
new file mode 100755
index 000000000000..2aaf6ea81715
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/userns_devmem.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Devmem tests for non-init userns.
+"""
+
+import os
+
+from devmem_lib import run_rx, run_rx_hds, run_tx, run_tx_chunks, setup_test
+from lib.py import NetDrvContEnv, ksft_disruptive, ksft_exit, ksft_run
+
+
+@ksft_disruptive
+def check_userns_rx(cfg) -> None:
+ """Run the devmem RX test through non-init userns netkit."""
+ run_rx(cfg)
+
+
+@ksft_disruptive
+def check_userns_tx(cfg) -> None:
+ """Run the devmem TX test through non-init userns netkit."""
+ run_tx(cfg)
+
+
+@ksft_disruptive
+def check_userns_tx_chunks(cfg) -> None:
+ """Run the devmem TX chunking test through non-init userns netkit."""
+ run_tx_chunks(cfg)
+
+
+def check_userns_rx_hds(cfg) -> None:
+ """Run the HDS test through non-init userns netkit."""
+ run_rx_hds(cfg)
+
+
+def main() -> None:
+ """Run userns devmem RX selftests against the test environment."""
+ with NetDrvContEnv(__file__, userns=True, rxqueues=2,
+ primary_rx_redirect=True) as cfg:
+ setup_test(cfg,
+ os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "ncdevmem"))
+ ksft_run([check_userns_rx, check_userns_tx, check_userns_tx_chunks,
+ check_userns_rx_hds], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()