From: Jakub Kicinski <kuba@kernel.org>
To: daniel.zahka@gmail.com, willemdebruijn.kernel@gmail.com
Cc: edumazet@google.com, cratiu@nvidia.com, borisp@nvidia.com,
kuniyu@google.com, netdev@vger.kernel.org,
Jakub Kicinski <kuba@kernel.org>
Subject: [RFC net-next 3/6] selftests: drv-net: psp: move the PSP test plumbing into psp_lib.py
Date: Sat, 22 Aug 2026 15:55:21 -0700 [thread overview]
Message-ID: <20260822225524.2328465-4-kuba@kernel.org> (raw)
In-Reply-To: <20260822225524.2328465-1-kuba@kernel.org>
Pure refactor, no behaviour change. A second PSP test is coming and
would otherwise need byte-identical copies of the responder chatter,
connection setup and data helpers.
Only what would be copied verbatim moves; the test cases and everything
specific to them stay in psp.py. main()'s responder spawning becomes a
context manager, which is the one place the shape changes.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
MAINTAINERS | 1 +
tools/testing/selftests/drivers/net/Makefile | 4 +
tools/testing/selftests/drivers/net/psp.py | 287 +++++-------------
.../testing/selftests/drivers/net/psp_lib.py | 179 +++++++++++
4 files changed, 265 insertions(+), 206 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/psp_lib.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 460cb7268845..536fbc1d244d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21770,6 +21770,7 @@ F: include/net/psp/
F: include/net/psp.h
F: include/uapi/linux/psp.h
F: net/psp/
+F: tools/testing/selftests/drivers/net/psp*
K: struct\ psp(_assoc|_dev|hdr)\b
PSTORE FILESYSTEM
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
index d5bf4cb638a8..de6e4d7f2dda 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -27,6 +27,10 @@ TEST_PROGS := \
xdp.py \
# end of TEST_PROGS
+TEST_FILES := \
+ psp_lib.py \
+ #
+
# YNL files, must be before "include ..lib.mk"
YNL_GEN_FILES := psp_responder
TEST_GEN_FILES += $(YNL_GEN_FILES)
diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index 315648a770d0..766d263803b1 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -4,11 +4,8 @@
"""Test suite for PSP capable drivers."""
import errno
-import fcntl
import os
import socket
-import struct
-import termios
import time
from lib.py import defer
@@ -20,136 +17,38 @@ from lib.py import KsftSkipEx, KsftFailEx
from lib.py import NetDrvEpEnv, NetDrvContEnv
from lib.py import Netlink, NlError, PSPFamily, RtnlFamily
from lib.py import NetNSEnter
-from lib.py import bkg, rand_port, wait_port_listen
from lib.py import ip
-
-def _get_outq(s):
- one = b'\0' * 4
- outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one)
- return struct.unpack("I", outq)[0]
-
-
-def _send_with_ack(cfg, msg):
- cfg.comm_sock.send(msg)
- response = cfg.comm_sock.recv(4)
- if response != b'ack\0':
- raise RuntimeError("Unexpected server response", response)
-
-
-def _remote_read_len(cfg):
- cfg.comm_sock.send(b'read len\0')
- return int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
-
-
-def _make_clr_conn(cfg, ipver=None):
- _send_with_ack(cfg, b'conn clr\0')
- remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
- s = socket.create_connection((remote_addr, cfg.comm_port), )
- return s
-
-
-def _make_psp_conn(cfg, version=0, ipver=None):
- _send_with_ack(cfg, b'conn psp\0' + struct.pack('BB', version, version))
- remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
- s = socket.create_connection((remote_addr, cfg.comm_port), )
- return s
-
-
-def _close_conn(cfg, s):
- _send_with_ack(cfg, b'data close\0')
- s.close()
+from psp_lib import check_data_rx, close_conn, get_outq, get_stat, \
+ init_psp_dev, make_clr_conn, make_psp_conn, send_careful, spi_xchg
+from psp_lib import responder as psp_responder
def _close_psp_conn(cfg, s):
- _close_conn(cfg, s)
-
-
-def _spi_xchg(s, rx):
- s.send(struct.pack('I', rx['spi']) + rx['key'])
- tx = s.recv(4 + len(rx['key']))
- return {
- 'spi': struct.unpack('I', tx[:4])[0],
- 'key': tx[4:]
- }
-
-
-def _send_careful(cfg, s, rounds):
- data = b'0123456789' * 200
- for i in range(rounds):
- n = 0
- for _ in range(10): # allow 10 retries
- try:
- n += s.send(data[n:], socket.MSG_DONTWAIT)
- if n == len(data):
- break
- except BlockingIOError:
- time.sleep(0.05)
- else:
- rlen = _remote_read_len(cfg)
- outq = _get_outq(s)
- report = f'sent: {i * len(data) + n} remote len: {rlen} outq: {outq}'
- raise RuntimeError(report)
-
- return len(data) * rounds
-
-
-def _check_data_rx(cfg, exp_len):
- read_len = -1
- for _ in range(30):
- cfg.comm_sock.send(b'read len\0')
- read_len = int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
- if read_len == exp_len:
- break
- time.sleep(0.01)
- ksft_eq(read_len, exp_len)
+ close_conn(cfg, s)
def _check_data_outq(s, exp_len, force_wait=False):
outq = 0
for _ in range(10):
- outq = _get_outq(s)
+ outq = get_outq(s)
if not force_wait and outq == exp_len:
break
time.sleep(0.01)
ksft_eq(outq, exp_len)
-def _get_stat(cfg, key):
- return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key]
-
#
# Test case boiler plate
#
-def _init_psp_dev(cfg, use_psp_ifindex=False):
- if not hasattr(cfg, 'psp_dev_id'):
- # Figure out which local device we are testing against
- # For NetDrvContEnv: use psp_ifindex instead of ifindex
- target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
- for dev in cfg.pspnl.dev_get({}, dump=True):
- if dev['ifindex'] == target_ifindex:
- cfg.psp_info = dev
- cfg.psp_dev_id = cfg.psp_info['id']
- break
- else:
- raise KsftSkipEx("No PSP devices found")
-
- # Enable PSP if necessary
- cap = cfg.psp_info['psp-versions-cap']
- ena = cfg.psp_info['psp-versions-ena']
- if cap != ena:
- cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
- defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
- 'psp-versions-ena': ena })
-
#
# Test cases
#
def dev_list_devices(cfg):
""" Dump all devices """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
devices = cfg.pspnl.dev_get({}, dump=True)
@@ -161,7 +60,7 @@ from lib.py import ip
def dev_get_device(cfg):
""" Get the device we intend to use """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
ksft_eq(dev['id'], cfg.psp_dev_id)
@@ -180,22 +79,22 @@ from lib.py import ip
def dev_rotate(cfg):
""" Test key rotation """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- prev_rotations = _get_stat(cfg, 'key-rotations')
+ prev_rotations = get_stat(cfg, 'key-rotations')
rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
ksft_eq(rot['id'], cfg.psp_dev_id)
rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
ksft_eq(rot['id'], cfg.psp_dev_id)
- cur_rotations = _get_stat(cfg, 'key-rotations')
+ cur_rotations = get_stat(cfg, 'key-rotations')
ksft_eq(cur_rotations, prev_rotations + 2)
def dev_rotate_spi(cfg):
""" Test key rotation and SPI check """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
top_a = top_b = 0
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
@@ -217,7 +116,7 @@ from lib.py import ip
def assoc_basic(cfg):
""" Test creating associations """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
assoc = cfg.pspnl.rx_assoc({"version": 0,
@@ -237,7 +136,7 @@ from lib.py import ip
def assoc_bad_dev(cfg):
""" Test creating associations with bad device ID """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
with ksft_raises(NlError) as cm:
@@ -249,23 +148,23 @@ from lib.py import ip
def assoc_sk_only_conn(cfg):
""" Test creating associations based on socket """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- with _make_clr_conn(cfg) as s:
+ with make_clr_conn(cfg) as s:
assoc = cfg.pspnl.rx_assoc({"version": 0,
"sock-fd": s.fileno()})
ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
cfg.pspnl.tx_assoc({"version": 0,
"tx-key": assoc['rx-key'],
"sock-fd": s.fileno()})
- _close_conn(cfg, s)
+ close_conn(cfg, s)
def assoc_sk_only_mismatch(cfg):
""" Test creating associations based on socket (dev mismatch) """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- with _make_clr_conn(cfg) as s:
+ with make_clr_conn(cfg) as s:
with ksft_raises(NlError) as cm:
cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id + 1234567,
@@ -273,14 +172,14 @@ from lib.py import ip
the_exception = cm.exception
ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
- _close_conn(cfg, s)
+ close_conn(cfg, s)
def assoc_sk_only_mismatch_tx(cfg):
""" Test creating associations based on socket (dev mismatch) """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- with _make_clr_conn(cfg) as s:
+ with make_clr_conn(cfg) as s:
with ksft_raises(NlError) as cm:
assoc = cfg.pspnl.rx_assoc({"version": 0,
"sock-fd": s.fileno()})
@@ -291,12 +190,12 @@ from lib.py import ip
the_exception = cm.exception
ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
- _close_conn(cfg, s)
+ close_conn(cfg, s)
def assoc_sk_only_unconn(cfg):
""" Test creating associations based on socket (unconnected, should fail) """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
with ksft_raises(NlError) as cm:
@@ -309,7 +208,7 @@ from lib.py import ip
def assoc_version_mismatch(cfg):
""" Test creating associations where Rx and Tx PSP versions do not match """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
versions = list(cfg.psp_info['psp-versions-cap'])
if len(versions) < 2:
@@ -335,7 +234,7 @@ from lib.py import ip
def assoc_twice(cfg):
""" Test reusing Tx assoc for two sockets """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
def rx_assoc_check(s):
assoc = cfg.pspnl.rx_assoc({"version": 0,
@@ -369,7 +268,7 @@ from lib.py import ip
def _data_basic_send(cfg, version, ipver):
""" Test basic data send """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
# Version 0 is required by spec, don't let it skip
if version:
@@ -383,21 +282,21 @@ from lib.py import ip
ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
raise KsftSkipEx("PSP version not supported", name)
- s = _make_psp_conn(cfg, version, ipver)
+ s = make_psp_conn(cfg, version, ipver)
rx_assoc = cfg.pspnl.rx_assoc({"version": version,
"dev-id": cfg.psp_dev_id,
"sock-fd": s.fileno()})
rx = rx_assoc['rx-key']
- tx = _spi_xchg(s, rx)
+ tx = spi_xchg(s, rx)
cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
"version": version,
"tx-key": tx,
"sock-fd": s.fileno()})
- data_len = _send_careful(cfg, s, 100)
- _check_data_rx(cfg, data_len)
+ data_len = send_careful(cfg, s, 100)
+ check_data_rx(cfg, data_len)
_close_psp_conn(cfg, s)
@@ -410,63 +309,63 @@ from lib.py import ip
"tx-key": tx,
"sock-fd": s.fileno()})
- data_len = _send_careful(cfg, s, 20)
+ data_len = send_careful(cfg, s, 20)
_check_data_outq(s, data_len, force_wait=True)
- _check_data_rx(cfg, 0)
+ check_data_rx(cfg, 0)
_close_psp_conn(cfg, s)
def data_send_bad_key(cfg):
""" Test send data with bad key """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- s = _make_psp_conn(cfg)
+ s = make_psp_conn(cfg)
rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id,
"sock-fd": s.fileno()})
rx = rx_assoc['rx-key']
- tx = _spi_xchg(s, rx)
+ tx = spi_xchg(s, rx)
tx['key'] = (tx['key'][0] ^ 0xff).to_bytes(1, 'little') + tx['key'][1:]
__bad_xfer_do(cfg, s, tx)
def data_send_disconnect(cfg):
""" Test socket close after sending data """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- with _make_psp_conn(cfg) as s:
+ with make_psp_conn(cfg) as s:
assoc = cfg.pspnl.rx_assoc({"version": 0,
"sock-fd": s.fileno()})
- tx = _spi_xchg(s, assoc['rx-key'])
+ tx = spi_xchg(s, assoc['rx-key'])
cfg.pspnl.tx_assoc({"version": 0,
"tx-key": tx,
"sock-fd": s.fileno()})
- data_len = _send_careful(cfg, s, 100)
- _check_data_rx(cfg, data_len)
+ data_len = send_careful(cfg, s, 100)
+ check_data_rx(cfg, data_len)
s.shutdown(socket.SHUT_RDWR)
s.close()
def _data_mss_adjust(cfg, ipver):
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
# First figure out what the MSS would be without any adjustments
- s = _make_clr_conn(cfg, ipver)
+ s = make_clr_conn(cfg, ipver)
s.send(b"0123456789abcdef" * 1024)
- _check_data_rx(cfg, 16 * 1024)
+ check_data_rx(cfg, 16 * 1024)
mss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
- _close_conn(cfg, s)
+ close_conn(cfg, s)
- s = _make_psp_conn(cfg, 0, ipver)
+ s = make_psp_conn(cfg, 0, ipver)
try:
rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id,
"sock-fd": s.fileno()})
rx = rx_assoc['rx-key']
- tx = _spi_xchg(s, rx)
+ tx = spi_xchg(s, rx)
rxmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
ksft_eq(mss, rxmss)
@@ -479,8 +378,8 @@ from lib.py import ip
txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
ksft_eq(mss, txmss + 40)
- data_len = _send_careful(cfg, s, 100)
- _check_data_rx(cfg, data_len)
+ data_len = send_careful(cfg, s, 100)
+ check_data_rx(cfg, data_len)
_check_data_outq(s, 0)
txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
@@ -491,30 +390,30 @@ from lib.py import ip
def data_stale_key(cfg):
""" Test send on a double-rotated key """
- _init_psp_dev(cfg)
+ init_psp_dev(cfg)
- prev_stale = _get_stat(cfg, 'stale-events')
- s = _make_psp_conn(cfg)
+ prev_stale = get_stat(cfg, 'stale-events')
+ s = make_psp_conn(cfg)
try:
rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id,
"sock-fd": s.fileno()})
rx = rx_assoc['rx-key']
- tx = _spi_xchg(s, rx)
+ tx = spi_xchg(s, rx)
cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
"version": 0,
"tx-key": tx,
"sock-fd": s.fileno()})
- data_len = _send_careful(cfg, s, 100)
- _check_data_rx(cfg, data_len)
+ data_len = send_careful(cfg, s, 100)
+ check_data_rx(cfg, data_len)
_check_data_outq(s, 0)
cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
- cur_stale = _get_stat(cfg, 'stale-events')
+ cur_stale = get_stat(cfg, 'stale-events')
ksft_gt(cur_stale, prev_stale)
s.send(b'0123456789' * 200)
@@ -544,7 +443,7 @@ from lib.py import ip
# netdevsim only for now
cfg.require_nsim()
- s = _make_clr_conn(cfg)
+ s = make_clr_conn(cfg)
try:
rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id,
@@ -553,7 +452,7 @@ from lib.py import ip
__nsim_psp_rereg(cfg)
finally:
- _close_conn(cfg, s)
+ close_conn(cfg, s)
def removal_device_bi(cfg):
@@ -564,7 +463,7 @@ from lib.py import ip
# netdevsim only for now
cfg.require_nsim()
- s = _make_clr_conn(cfg)
+ s = make_clr_conn(cfg)
try:
rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
"dev-id": cfg.psp_dev_id,
@@ -575,7 +474,7 @@ from lib.py import ip
"sock-fd": s.fileno()})
__nsim_psp_rereg(cfg)
finally:
- _close_conn(cfg, s)
+ close_conn(cfg, s)
def _get_psp_ver_ip_variants():
@@ -631,21 +530,21 @@ from lib.py import ip
with NetNSEnter(cfg.netns.name):
cfg.pspnl = PSPFamily()
- sock = _make_psp_conn(cfg, version, ipver)
+ sock = make_psp_conn(cfg, version, ipver)
rx_assoc = cfg.pspnl.rx_assoc({"version": version,
"dev-id": cfg.psp_dev_id,
"sock-fd": sock.fileno()})
rx_key = rx_assoc['rx-key']
- tx_key = _spi_xchg(sock, rx_key)
+ tx_key = spi_xchg(sock, rx_key)
cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
"version": version,
"tx-key": tx_key,
"sock-fd": sock.fileno()})
- data_len = _send_careful(cfg, sock, 100)
- _check_data_rx(cfg, data_len)
+ data_len = send_careful(cfg, sock, 100)
+ check_data_rx(cfg, data_len)
_close_psp_conn(cfg, sock)
@@ -766,7 +665,7 @@ from lib.py import ip
def _dev_assoc_no_nsid(cfg):
""" Test dev-assoc and dev-disassoc without nsid attribute """
- _init_psp_dev(cfg, True)
+ init_psp_dev(cfg, True)
# Associate without nsid - should look up ifindex in caller's netns
cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
@@ -800,7 +699,7 @@ from lib.py import ip
Creates a disposable netkit pair for this test to avoid destroying
the shared environment.
"""
- _init_psp_dev(cfg, True)
+ init_psp_dev(cfg, True)
defer(delattr, cfg, 'psp_dev_id')
defer(delattr, cfg, 'psp_info')
@@ -877,7 +776,7 @@ from lib.py import ip
def _assoc_nk_guest(cfg):
"""Associate nk_guest with PSP device and register cleanup via defer()."""
- _init_psp_dev(cfg, True)
+ init_psp_dev(cfg, True)
cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
'ifindex': cfg.nk_guest_ifindex,
@@ -937,7 +836,6 @@ from lib.py import ip
cfg.psp_dev_peer_nsid = _get_nsid(cfg.netns.name)
-
def main() -> None:
""" Ksft boiler plate main """
@@ -960,46 +858,23 @@ from lib.py import ip
# Set up responder and communication sock
# psp_responder runs in _netns (remote namespace with psp_dev_peer)
- responder = cfg.remote.deploy("psp_responder")
+ with psp_responder(cfg):
+ cases = [data_basic_send, data_mss_adjust]
- cfg.comm_port = rand_port()
- srv = None
- try:
- with bkg(responder + f" -p {cfg.comm_port} -i {cfg.remote_ifindex}",
- host=cfg.remote, exit_wait=True) as srv:
- wait_port_listen(cfg.comm_port, host=cfg.remote)
+ if has_cont:
+ cases += [
+ _assoc_check_list,
+ data_basic_send_netkit_psp_assoc,
+ _key_rotation_notify_multi_ns_netkit,
+ _dev_change_notify_multi_ns_netkit,
+ _psp_dev_get_check_netkit_psp_assoc,
+ _dev_assoc_no_nsid,
+ _psp_dev_assoc_cleanup_on_netkit_del,
+ ]
- cfg.comm_sock = socket.create_connection((cfg.remote_addr,
- cfg.comm_port),
- timeout=1)
-
- cases = [data_basic_send, data_mss_adjust]
-
- if has_cont:
- cases += [
- _assoc_check_list,
- data_basic_send_netkit_psp_assoc,
- _key_rotation_notify_multi_ns_netkit,
- _dev_change_notify_multi_ns_netkit,
- _psp_dev_get_check_netkit_psp_assoc,
- _dev_assoc_no_nsid,
- _psp_dev_assoc_cleanup_on_netkit_del,
- ]
-
- ksft_run(cases=cases, globs=globals(),
- case_pfx={"dev_", "data_", "assoc_", "removal_"},
- args=(cfg, ))
-
- cfg.comm_sock.send(b"exit\0")
- cfg.comm_sock.close()
- finally:
- if srv and (srv.stdout or srv.stderr):
- ksft_pr("")
- ksft_pr(f"Responder logs ({srv.ret}):")
- if srv and srv.stdout:
- ksft_pr("STDOUT:\n# " + srv.stdout.strip().replace("\n", "\n# "))
- if srv and srv.stderr:
- ksft_pr("STDERR:\n# " + srv.stderr.strip().replace("\n", "\n# "))
+ ksft_run(cases=cases, globs=globals(),
+ case_pfx={"dev_", "data_", "assoc_"},
+ args=(cfg, ))
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/psp_lib.py b/tools/testing/selftests/drivers/net/psp_lib.py
new file mode 100644
index 000000000000..1fc4bff84fb1
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/psp_lib.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Helpers shared by the PSP tests.
+
+Only code which the tests would otherwise have to copy verbatim belongs
+here, mostly talking to psp_responder on the other end of the link.
+"""
+
+import fcntl
+import socket
+import struct
+import termios
+import time
+from contextlib import contextmanager
+
+from lib.py import defer
+from lib.py import ksft_eq, ksft_pr
+from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import bkg, rand_port, wait_port_listen
+
+
+def get_outq(s):
+ one = b'\0' * 4
+ outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one)
+ return struct.unpack("I", outq)[0]
+
+
+def send_with_ack(cfg, msg):
+ cfg.comm_sock.send(msg)
+ response = cfg.comm_sock.recv(4)
+ if response != b'ack\0':
+ raise RuntimeError("Unexpected server response", response)
+
+
+def remote_read_len(cfg):
+ cfg.comm_sock.send(b'read len\0')
+ return int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
+
+
+def make_clr_conn(cfg, ipver=None):
+ send_with_ack(cfg, b'conn clr\0')
+ remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
+ s = socket.create_connection((remote_addr, cfg.comm_port), )
+ return s
+
+
+def make_psp_conn(cfg, version=0, ipver=None):
+ send_with_ack(cfg, b'conn psp\0' + struct.pack('BB', version, version))
+ remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
+ s = socket.create_connection((remote_addr, cfg.comm_port), )
+ return s
+
+
+def close_conn(cfg, s):
+ send_with_ack(cfg, b'data close\0')
+ s.close()
+
+
+def spi_xchg(s, rx):
+ s.send(struct.pack('I', rx['spi']) + rx['key'])
+ tx = s.recv(4 + len(rx['key']))
+ return {
+ 'spi': struct.unpack('I', tx[:4])[0],
+ 'key': tx[4:]
+ }
+
+
+def send_careful(cfg, s, rounds):
+ data = b'0123456789' * 200
+ for i in range(rounds):
+ n = 0
+ for _ in range(10): # allow 10 retries
+ try:
+ n += s.send(data[n:], socket.MSG_DONTWAIT)
+ if n == len(data):
+ break
+ except BlockingIOError:
+ time.sleep(0.05)
+ else:
+ rlen = remote_read_len(cfg)
+ outq = get_outq(s)
+ report = f'sent: {i * len(data) + n} remote len: {rlen} outq: {outq}'
+ raise RuntimeError(report)
+
+ return len(data) * rounds
+
+
+def check_data_rx(cfg, exp_len):
+ read_len = -1
+ for _ in range(30):
+ cfg.comm_sock.send(b'read len\0')
+ read_len = int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
+ if read_len == exp_len:
+ break
+ time.sleep(0.01)
+ ksft_eq(read_len, exp_len)
+
+
+def get_stat(cfg, key):
+ return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key]
+
+def init_psp_dev(cfg, use_psp_ifindex=False):
+ if not hasattr(cfg, 'psp_dev_id'):
+ # Figure out which local device we are testing against
+ # For NetDrvContEnv: use psp_ifindex instead of ifindex
+ target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
+ for dev in cfg.pspnl.dev_get({}, dump=True):
+ if dev['ifindex'] == target_ifindex:
+ cfg.psp_info = dev
+ cfg.psp_dev_id = cfg.psp_info['id']
+ break
+ else:
+ raise KsftSkipEx("No PSP devices found")
+
+ # Enable PSP if necessary
+ cap = cfg.psp_info['psp-versions-cap']
+ ena = cfg.psp_info['psp-versions-ena']
+ if cap != ena:
+ cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
+ defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
+ 'psp-versions-ena': ena })
+
+
+def recv_careful(s, target, rounds=100):
+ """Read exactly target bytes, tolerating short reads"""
+ data = b''
+ for _ in range(rounds):
+ try:
+ data += s.recv(target - len(data), socket.MSG_DONTWAIT)
+ if len(data) == target:
+ return data
+ except BlockingIOError:
+ time.sleep(0.001)
+ raise KsftFailEx(f"short read, got {len(data)} of {target} bytes")
+
+
+def req_echo(cfg, s):
+ """Ask the peer to echo, and check the reply arrives intact"""
+ send_with_ack(cfg, b'data echo\0')
+ ksft_eq(recv_careful(s, 5), b'echo\0')
+
+
+def psp_txrx(cfg, s, rounds, sent=0):
+ """Send data both ways, and return the total bytes sent to the peer"""
+ sent += send_careful(cfg, s, rounds)
+ check_data_rx(cfg, sent)
+ req_echo(cfg, s)
+ return sent
+
+
+@contextmanager
+def responder(cfg):
+ """Run psp_responder on the remote end and open the comm socket to it"""
+ binary = cfg.remote.deploy("psp_responder")
+
+ cfg.comm_port = rand_port()
+ srv = None
+ try:
+ with bkg(binary + f" -p {cfg.comm_port} -i {cfg.remote_ifindex}",
+ host=cfg.remote, exit_wait=True) as srv:
+ wait_port_listen(cfg.comm_port, host=cfg.remote)
+
+ cfg.comm_sock = socket.create_connection((cfg.remote_addr,
+ cfg.comm_port),
+ timeout=1)
+ yield cfg
+
+ cfg.comm_sock.send(b"exit\0")
+ cfg.comm_sock.close()
+ finally:
+ if srv and (srv.stdout or srv.stderr):
+ ksft_pr("")
+ ksft_pr(f"Responder logs ({srv.ret}):")
+ if srv and srv.stdout:
+ ksft_pr("STDOUT:\n# " + srv.stdout.strip().replace("\n", "\n# "))
+ if srv and srv.stderr:
+ ksft_pr("STDERR:\n# " + srv.stderr.strip().replace("\n", "\n# "))
--
2.55.0
next prev parent reply other threads:[~2026-08-22 22:55 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-22 22:55 [RFC net-next 0/6] psp: use virt cookie as Rx steering hint Jakub Kicinski
2026-08-22 22:55 ` [RFC net-next 1/6] psp: steer Rx queues with the virtualization cookie Jakub Kicinski
2026-08-23 15:31 ` Daniel Zahka
2026-08-24 15:01 ` Jakub Kicinski
2026-08-24 15:09 ` Cosmin Ratiu
2026-08-24 15:19 ` Jakub Kicinski
2026-08-23 18:18 ` Willem de Bruijn
2026-08-22 22:55 ` [RFC net-next 2/6] netdevsim: support PSP VC based queue steering Jakub Kicinski
2026-08-22 22:55 ` Jakub Kicinski [this message]
2026-08-22 22:55 ` [RFC net-next 4/6] selftests: drv-net: psp_steer: test " Jakub Kicinski
2026-08-22 22:55 ` [RFC net-next 5/6] selftests: drv-net: psp_steer: test where PSP steering sits in the Rx pipeline Jakub Kicinski
2026-08-22 22:55 ` [RFC net-next 6/6] selftests: drv-net: psp_steer: cover corner cases and races Jakub Kicinski
2026-08-23 17:48 ` [RFC net-next 0/6] psp: use virt cookie as Rx steering hint Willem de Bruijn
2026-08-24 15:05 ` Cosmin Ratiu
2026-08-25 9:52 ` Cosmin Ratiu
2026-08-25 18:55 ` Jakub Kicinski
2026-08-24 15:11 ` Jakub Kicinski
2026-08-24 18:04 ` Willem de Bruijn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260822225524.2328465-4-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=borisp@nvidia.com \
--cc=cratiu@nvidia.com \
--cc=daniel.zahka@gmail.com \
--cc=edumazet@google.com \
--cc=kuniyu@google.com \
--cc=netdev@vger.kernel.org \
--cc=willemdebruijn.kernel@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox