From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DAA2738A71E for ; Tue, 28 Apr 2026 22:42:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.174 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777416147; cv=none; b=Y1nIddjOp5n7T0p3WewvuT3WQbfS8cghmu2FKIBvywoCvL7Hz1kJ9xRkVZbCgpka5SiQAF7l49x+yUhIS0TN4MK2trtNf3BEqOK3PExusvrOVGXML9d8ze820fKS1c/UBgbhf5EyFj+v9AM7hmaN8MyLGjBzkBiWEzoIODN2ujw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777416147; c=relaxed/simple; bh=Z+KSX41d6V07NDJYlZrWymWSIrW05h06dGJ5dmEO0so=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=N7R4AhH/SRQBJkhxoe8S0SwRl7OtcuWWyaKqW2m1Sr09ssOtqcX7a5OKBQjsAHov/ldqUdJ3SJbWSlj/Il85FKxpDi0Wc1jgCkawoJJJmHWPx6meqQf5FcrzUXmIc/kuF4a9ZayXtJD0mONp1sFM7T0KEuQnaxGUyzD0B+pxRXI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=HBzO3gi1; arc=none smtp.client-ip=209.85.215.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="HBzO3gi1" Received: by mail-pg1-f174.google.com with SMTP id 41be03b00d2f7-c70e27e2b74so4491034a12.0 for ; Tue, 28 Apr 2026 15:42:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777416144; x=1778020944; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=NwRH1bwDDN+6gtAVtp6tT94FrtoOSiX4OwTkZsArKRc=; b=HBzO3gi1gq1gQPrN8iRw+x6g3mq3W/igaNDqqYmiav4eWkTiC0ac08awPHzF8uTROj wOdq9Qbuh801CzcfrZsuIG8LMt++NTOxn57g0eD5QgrFa4enw6vG3A1BBo4eKIKaNK+j pQOhGYw8jx1jc4f9l5QT7ON5OLXE0Jq7KWqzHbAgYmC+Jt11I5cHysTsOtMyo5IYtFVZ nB9opP/+2UE/JhYjl/5AnSi95OVJ6Dr/XbYef1pKAWaR7a9oCPQw96AVxmAaqvGn/RNo CUWaOX6MB08FoQ+j955D6tNnjXiAXLrSTmcKhFwN1NigNdJKUpcxk5T2tM5iXb2gyyMf AK4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777416144; x=1778020944; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=NwRH1bwDDN+6gtAVtp6tT94FrtoOSiX4OwTkZsArKRc=; b=lvr34+cW0IIuUhsBGT59uUT6Ch7/nyao2Pko/BsSQdl50BdB9nIouR5s73BbfxVcy3 BMq7RwTs6Y+O8t/mTee/xo2u3PXwlNok2yUxl79Umz7Smo2HW2pN/qnccCkLnL9RZzwn 6mnrSKz44lDAHQUSQjpY9XsGo9jRwJGpT3ZQxOs7LkDhxXzDuPz8tuAcv+tJ6emhb6An 1hXaKCj/WxII6BZd949S4iR3QYNGSxzKIOMfzSRSoXyx42Jh9xtHHawZdCTbgQck1K7K VX9XM8PNV8hu6ls+Afw2ETv0H0b9ALiMSusBmrOwYIOsAwybqeWS4FWQnpTVGqMAKi0T 9BtA== X-Gm-Message-State: AOJu0YyOFuWWurY/ncGg0mlRrgTtJAJ4fsuz7eV//0pmdGeG8oxfiUrp wuN+AHSm+Zo/W02Daf3DHdf+FlT6BTyosFVjvmaYZa3dBXMKsQu2furY X-Gm-Gg: AeBDietPT/YhJ+TJYItoa3eKi2Y6RJl/TrOLpt79AiE4KZwGfTl1VoZvyLWwfb+drk4 IbjdPVl/Z6uPGVjOgw83CTblgETV1PDZRb+4ClrdA1UX0KNdAbePprelyXKuM+8S4MnAg9A0c+6 FnktLdNSVg71/AjnRD5hWMyG9Mu2YdDz2O9oVQNu/G0HC+U3ANktIULBflb4sCPTrFgiTYRKLcv jAYBRleWhBCoJAR85ll2b5x9abfp5Gus+oggacENbnAhFG1jtKXwECyHf0wBHMh78RTO/wShMV/ TMjCtGvuI3CikjaREKUr7/erGrez3o53DR7ruZCjEqfx2XiakOCxCHMtKVlodzkzB5UeoGlDLra OBDrscsGiyM/Ydmse8f2CwmPI1weYLXcV1Mv6v14480A//P1ovShM9qgL6iUVkahgG7L1DLkT/H DDIyXhPCoBF7K8FyeF0MoPvxux6We1Kg== X-Received: by 2002:a05:6a00:99f:b0:82f:6a64:deac with SMTP id d2e1a72fcca58-834ddbebdf9mr5161857b3a.28.1777416144009; Tue, 28 Apr 2026 15:42:24 -0700 (PDT) Received: from localhost ([2a03:2880:ff:70::]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-834ed80de21sm97981b3a.55.2026.04.28.15.42.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 28 Apr 2026 15:42:23 -0700 (PDT) From: Bobby Eshleman Date: Tue, 28 Apr 2026 15:42:06 -0700 Subject: [PATCH net-next 09/11] selftests: drv-net: refactor devmem command builders into lib module Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260428-tcp-dm-netkit-v1-9-719280eba4d2@meta.com> References: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com> In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Alex Shi , Yanteng Si , Dongliang Mu , Michael Chan , Pavan Chebbi , Joshua Washington , Harshitha Ramamurthy , Saeed Mahameed , Tariq Toukan , Mark Bloch , Leon Romanovsky , Alexander Duyck , kernel-team@meta.com, Daniel Borkmann , Nikolay Aleksandrov , Shuah Khan Cc: netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-rdma@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Stanislav Fomichev , Mina Almasry , Bobby Eshleman X-Mailer: b4 0.14.3 From: Bobby Eshleman Adding netkit-based devmem tests is a straight-forward copy of devmem test commands plus some args for the nk cases, so this patch breaks out these command builders into helpers used by both. Though we tried to avoid libraries to avoid increasing the barrier of entry/complexity (see selftests/drivers/net/README.md, section "Avoid libraries and frameworks"), factoring out these functions seemed like the lesser of two evils in this case of using the same commands, just with slightly different args per environment. I experimented with just having all of the tests in the same file to avoid having helpers in a library file, but because ksft_run() is limited to a single call per file, and the new tests will require different environments (NetDrvContEnv/NetDrvEpEnv), it would have been necessary to have each test set up its own environment instead of sharing one for the entire ksft_run() run. This came at the cost of ballooning the test time (from under 5s to 30s on my test system), so to strike a balance these tests were placed in separate files so they could keep a shared environment across a single ksft_run() run shared across all tests using the same env type (introduced in subsequent patches). The helpers work transparently with both plain and netkit environments by inspecting cfg for netkit-specific attributes (netns, nk_queue, etc...). No functional change to the existing devmem.py tests. Assisted-by: Claude Code:claude-sonnet-4-6 Signed-off-by: Bobby Eshleman --- tools/testing/selftests/drivers/net/hw/devmem.py | 73 +------ .../selftests/drivers/net/hw/lib/py/devmem.py | 215 +++++++++++++++++++++ 2 files changed, 224 insertions(+), 64 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py index ee863e90d1e0..33648e39577a 100755 --- a/tools/testing/selftests/drivers/net/hw/devmem.py +++ b/tools/testing/selftests/drivers/net/hw/devmem.py @@ -1,92 +1,37 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 +"""Test devmem TCP.""" from os import path -from lib.py import ksft_run, ksft_exit -from lib.py import ksft_eq, KsftSkipEx +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") +from lib.py.devmem import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds @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_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_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_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}" - - 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}") + run_rx_hds(cfg) def main() -> None: with NetDrvEpEnv(__file__) as cfg: - cfg.bin_local = path.abspath(path.dirname(__file__) + "/ncdevmem") - cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) - + setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem")) ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds], - args=(cfg, )) + args=(cfg,)) ksft_exit() diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py new file mode 100644 index 000000000000..e95fc38337fa --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py @@ -0,0 +1,215 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Shared helpers for devmem TCP selftests.""" + +import re + +from net.lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, + ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily, + NetdevFamily) + + +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") + + +def configure_nic(cfg): + """Channels, rings, RSS, queue lease for netkit devmem. + + Rings and RSS are re-applied each call because per-test defers restore + them after every test case. The queue lease is created only once. + """ + 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}}) + rx_rings = rings['rx'] + hds_thresh = rings.get('hds-thresh', 0) + orig_data_split = rings.get('tcp-data-split', 'unknown') + + ethnl.rings_set({'header': {'dev-index': cfg.ifindex}, + 'tcp-data-split': 'enabled', + 'hds-thresh': 0, + 'rx': min(64, rx_rings)}) + defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex}, + 'tcp-data-split': orig_data_split, + 'hds-thresh': hds_thresh, + 'rx': 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): + 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): + 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" + if verify: + extras += " -v 7" + else: + ifname = cfg.ifname + addr = cfg.addr + extras = "" + + if fail_on_linear: + extras += " -L" + + return f"{cfg.bin_local} -l -f {ifname} -s {addr} -p {port} {extras}" + + +def ncdevmem_tx(cfg, port, chunk_size=0): + """ncdevmem TX send command (without stdin pipe).""" + if hasattr(cfg, 'netns'): + ifname = cfg._nk_guest_ifname + addr = cfg.remote_addr_v['6'] + nk_args = "-t 0 -q 1 -n" + else: + ifname = cfg.ifname + addr = cfg.remote_addr + nk_args = "" + + chunk = f"-z {chunk_size}" if chunk_size else "" + + return (f"{cfg.bin_local} -f {ifname} -s {addr} -p {port}" + f" {nk_args} {chunk}").rstrip() + + +def socat_send(cfg, port, buf_size=0, nodelay=False, bind=False): + """Socat command for sending to the devmem listener.""" + proto = f"TCP{cfg.addr_ipver}" + + if hasattr(cfg, 'netns'): + addr = f"[{cfg.nk_guest_ipv6}]" + else: + addr = cfg.baddr + + buf = f"-b {buf_size} " if buf_size else "" + + suffix = "" + if nodelay: + suffix += ",nodelay" + # Match the 5-tuple flow rule ncdevmem installs when given -c. + if bind: + suffix += f",bind={cfg.remote_baddr}:{port}" + + return f"socat {buf}-u - {proto}:{addr}:{port}{suffix}" + + +def socat_listen(cfg, port): + """Socat listen command for TX tests.""" + proto = f"TCP{cfg.addr_ipver}" + + if hasattr(cfg, 'netns'): + opts = ",reuseaddr" + else: + opts = "" + + return f"socat -U - {proto}-LISTEN:{port}{opts}" + + +def setup_test(cfg, bin_local): + cfg.bin_local = bin_local + cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) + cfg.listen_ns = getattr(cfg, 'netns', None) + require_devmem(cfg) + + +def run_rx(cfg): + if hasattr(cfg, 'netns'): + 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}") + ns = getattr(cfg, "netns", None) + + listen_cmd = ncdevmem_rx(cfg, port) + with bkg(listen_cmd, exit_wait=True, ns=ns) as ncdevmem: + wait_port_listen(port, proto="tcp", ns=ns) + cmd(data_pipe, host=cfg.remote, shell=True) + ksft_eq(ncdevmem.ret, 0) + + +def run_tx(cfg): + if hasattr(cfg, 'netns'): + configure_nic(cfg) + ns = getattr(cfg, "netns", None) + port = rand_port() + tx = 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}'", ns=ns, shell=True) + ksft_eq(socat.stdout.strip(), "hello\nworld") + + +def run_tx_chunks(cfg): + if hasattr(cfg, 'netns'): + configure_nic(cfg) + ns = getattr(cfg, "netns", None) + port = rand_port() + tx = 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}'", ns=ns, shell=True) + ksft_eq(socat.stdout.strip(), "hello\nworld") + + +def run_rx_hds(cfg): + if hasattr(cfg, 'netns'): + configure_nic(cfg) + ns = 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, nodelay=True) + + with bkg(listen_cmd, exit_wait=True, ns=ns) as ncdevmem: + wait_port_listen(port, proto="tcp", ns=ns) + 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}") -- 2.52.0