From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qk1-f176.google.com (mail-qk1-f176.google.com [209.85.222.176]) (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 CC6DF2F0661 for ; Tue, 5 May 2026 00:28:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.176 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777940889; cv=none; b=ST911zRp3jZyAFT/9cRxmFDxqFSvfkQfU5zvaYzdJn64BvLMFGlm1WIzUBq54AjItzWw3eD53fKUY2lfwcuM+0WLKnXB6NjICUFwKIvnjqgWk0XD8LrQqzQBppJ6s5++syToQJ3bTF2PsDv+rOszW8NslkygHShjQHEvlHZpdxQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777940889; c=relaxed/simple; bh=Qa3qVnasFjeKcLXQGuewmxcuQRVtnPk652qg+JZlmVk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=VV6wIihlNCz+h9YSqo+MzNrOjyE0cgCnhXem6756gLK0krRKI/kcsEtr5bIFTstxZrxfFusNk6C7uTOmVsWlQwD2cgFZ4EoKf0eNbfsrq/gQEZ959RTXukiJF9sikqnfK/iN1uf//UC7O7b+Gt+bwXdmep5PwlYA/SopChx1odg= 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=YreCqpYl; arc=none smtp.client-ip=209.85.222.176 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="YreCqpYl" Received: by mail-qk1-f176.google.com with SMTP id af79cd13be357-8d4f78fc9f6so512167785a.3 for ; Mon, 04 May 2026 17:28:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777940885; x=1778545685; 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=uKo0X1zLy79YbJbsw8fyk970YAWWmEL6K2YwtNFNBfc=; b=YreCqpYlAMqim2AuMSaAkWfdZekd3oeuX/xVg1OR8ndHDulJrVaU/rZ4zmz8ED6HnR FjFRgQrbxqkOxnwbQZSUDGvWfa9vYhOFjdDcAz3cs1CRdB/B6HtM3atQw4SO+2h/2dOl hihuRDLbm8pycePtGr5yBu0iB0B2bx0kfXjX4XNsX4FzX7JPlN5S8Xw9+wCuaAnYLvqj oTLlbqNbb+6AfhJ82teP7w8L+n5WJb8NehPGuXmYbG5/PLakmo1JYLIoXY5SWejJoY7i mxZHOBRiYwfsM/P7Qojt71Wv3/P8GAqQbynXm3aU2tzzye9jYf1dzAgRQnCDLqSTTnMa 7mgQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777940885; x=1778545685; 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=uKo0X1zLy79YbJbsw8fyk970YAWWmEL6K2YwtNFNBfc=; b=Q28pOfl3XhZauyt2JvqcgxN+F+nPaWhtKhOKeBJaK0D3sdLFqNRzio+jPlPzjF5RJX u+agoE2ObDb7+uPIb/cmZJD4EvzIpWZlgnF3TFriHKUC+/snJYYJ9EwyjB127RdGd9PC FdSvz2wG0UWbO7hH3flpUKMeATP5QJSrHlTnv0B3lTTOjaCcFeeBAMyhFCHttA2UG7U7 WFLP9UoP80zrGjmZy7FsgFWtvzoqV2El49OW17RyKCITCh9M9NUH5q4rRZRIbNVzHFc9 Xd3JMPVXtBLNgicZT1btBGvkVpmywNsVGwItSOOSqIxN0/qig9inpgVj2Z/GYdOJLJLL /fzQ== X-Gm-Message-State: AOJu0YyiLi+SMDKSHwrV2616sswPpRtM9uvZnZOG/DuyEJPqmewrF+ZF mvP5+22k3L4ohR+JjnjKp1W798nxoDISAHXfIGLGyO0tprVZlkvttXYg X-Gm-Gg: AeBDieupnOCZjnsAjPpTxZCQX7RHDfZnSItdxL9i64/SOvtRyx+e1LhLK0niaOEBY97 j/EII156WfdEuvop8KMXpCpYc78o1EkLsM1BPQWjtooi3mHXU0L6RHgFQ9wLegF6Nb8dlmVDiWD H0WFggV8hD/spuBRonHHlUFyVDjl/mL+ZdyvYRBdPaAcgf2es94VHXvdemfJbkUZ9C6Fbz3vyd+ LLgTDQ+RVSS8e+SSgceLhxQ0KFoHdoPPIVbZuOm8Id5GW9c9sR8yNJsrnGbmVCCauV9MsXH+maE Ro3zFpfP7AD1p9e3gnNNhWZhLc9Z8/BsWG/14vDbnN/I1lRzeEnPiuAANAlqkXALuWXkFFbIzFc 5f0IntQh+E04PxVNHdW3BXLUYWn6oZ7nj5JJIHz1dpm4Xlb4lMS2ANBeeXPBpcBsZpOEo5g0c2V Cvqv5rZBmY+Nm6Bh4I/mZZN4xYLCZwuVRC3UH2yBkOsKYiFDAOhJJljA== X-Received: by 2002:a05:620a:4722:b0:8cd:8f04:50ec with SMTP id af79cd13be357-902e49b6e22mr192973685a.2.1777940884520; Mon, 04 May 2026 17:28:04 -0700 (PDT) Received: from localhost ([2a03:2880:f800:29::]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8fc29a7f4dbsm1210668885a.16.2026.05.04.17.28.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 04 May 2026 17:28:04 -0700 (PDT) From: Bobby Eshleman Date: Mon, 04 May 2026 17:27:51 -0700 Subject: [PATCH net-next v2 4/6] 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: <20260504-tcp-dm-netkit-v2-4-56d52ac72fd4@meta.com> References: <20260504-tcp-dm-netkit-v2-0-56d52ac72fd4@meta.com> In-Reply-To: <20260504-tcp-dm-netkit-v2-0-56d52ac72fd4@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...). Signed-off-by: Bobby Eshleman --- Changes in v2: - Move require_devmem() into individual test functions so KsftSkipEx goes up to ksft_run() (Sashiko) - in ncdevmem_rx(), move -v 7 to take effect for both netns and non-netns when verify=True --- tools/testing/selftests/drivers/net/hw/devmem.py | 73 +------ .../selftests/drivers/net/hw/lib/py/devmem.py | 222 +++++++++++++++++++++ 2 files changed, 231 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..6f8a3f5aae14 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py @@ -0,0 +1,222 @@ +# 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, flow_steer=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" + else: + ifname = cfg.ifname + addr = cfg.addr + + extras = "" + if flow_steer: + extras += f"-c {cfg.remote_addr}" + + if verify: + extras += " -v 7" + + 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) + + +def run_rx(cfg): + require_devmem(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): + require_devmem(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): + require_devmem(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): + require_devmem(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