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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.