Netdev List
 help / color / mirror / Atom feed
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 4/6] selftests: drv-net: psp_steer: test PSP VC based queue steering
Date: Sat, 22 Aug 2026 15:55:22 -0700	[thread overview]
Message-ID: <20260822225524.2328465-5-kuba@kernel.org> (raw)
In-Reply-To: <20260822225524.2328465-1-kuba@kernel.org>

Cover the netlink interface for PSP VC steering.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/testing/selftests/drivers/net/Makefile  |   1 +
 .../selftests/drivers/net/psp_responder.c     |  42 +++
 .../testing/selftests/drivers/net/psp_lib.py  |   5 +
 .../selftests/drivers/net/psp_steer.py        | 256 ++++++++++++++++++
 4 files changed, 304 insertions(+)
 create mode 100644 tools/testing/selftests/drivers/net/psp_steer.py

diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
index de6e4d7f2dda..e8719fc106ba 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -19,6 +19,7 @@ TEST_PROGS := \
 	netpoll_basic.py \
 	ping.py \
 	psp.py \
+	psp_steer.py \
 	queues.py \
 	ring_reconfig.py \
 	shaper.py \
diff --git a/tools/testing/selftests/drivers/net/psp_responder.c b/tools/testing/selftests/drivers/net/psp_responder.c
index a26e7628bbb1..f8df6b8da9b4 100644
--- a/tools/testing/selftests/drivers/net/psp_responder.c
+++ b/tools/testing/selftests/drivers/net/psp_responder.c
@@ -23,6 +23,7 @@ static bool should_quit;
 struct opts {
 	int port;
 	int ifindex;
+	int devid;
 	bool verbose;
 };
 
@@ -118,6 +119,36 @@ static void send_str(int sock, int value)
 	send(sock, buf, ret + 1, MSG_WAITALL);
 }
 
+static void
+handle_dev_steer(struct ynl_sock *ys, struct opts *opts, char *data,
+		 int comm_sock)
+{
+	struct psp_dev_set_req *req;
+	struct psp_dev_set_rsp *rsp;
+
+	if (opts->devid < 0) {
+		fprintf(stderr, "WARN: dev steer but no PSP device\n");
+		send_err(comm_sock);
+		return;
+	}
+
+	req = psp_dev_set_req_alloc();
+
+	psp_dev_set_req_set_id(req, opts->devid);
+	psp_dev_set_req_set_vc_steer_ena(req, *data);
+
+	rsp = psp_dev_set(ys, req);
+	psp_dev_set_req_free(req);
+	if (!rsp) {
+		perror("ERROR: failed to set device features");
+		send_err(comm_sock);
+		return;
+	}
+	psp_dev_set_rsp_free(rsp);
+
+	send_ack(comm_sock);
+}
+
 static void
 run_session(struct ynl_sock *ys, struct opts *opts,
 	    int server_sock, int comm_sock)
@@ -210,6 +241,9 @@ run_session(struct ynl_sock *ys, struct opts *opts,
 			match;						\
 		})
 
+#define cmd_w_msg(_name, _type)						\
+		(off >= sizeof(_name) + sizeof(_type) && cmd(_name))
+
 			do {
 				consumed = false;
 
@@ -224,6 +258,11 @@ run_session(struct ynl_sock *ys, struct opts *opts,
 						fprintf(stderr, "WARN: echo but no data sock\n");
 					send_ack(comm_sock);
 				}
+				if (cmd_w_msg("dev steer", __u8)) {
+					handle_dev_steer(ys, opts, buf,
+							 comm_sock);
+					__consume(sizeof(__u8));
+				}
 				if (cmd("data close")) {
 					if (data_sock >= 0) {
 						close(data_sock);
@@ -254,6 +293,7 @@ run_session(struct ynl_sock *ys, struct opts *opts,
 				}
 				if (cmd("exit"))
 					should_quit = true;
+#undef cmd_w_msg
 #undef cmd
 
 				if (!consumed) {
@@ -461,6 +501,8 @@ int main(int argc, char **argv)
 			goto err_close;
 	}
 
+	opts.devid = devid;
+
 	ret = run_responder(ys, &opts);
 
 	if (devid >= 0 && ver_ena != ver_cap &&
diff --git a/tools/testing/selftests/drivers/net/psp_lib.py b/tools/testing/selftests/drivers/net/psp_lib.py
index 1fc4bff84fb1..b2bf205ed327 100644
--- a/tools/testing/selftests/drivers/net/psp_lib.py
+++ b/tools/testing/selftests/drivers/net/psp_lib.py
@@ -58,6 +58,11 @@ from lib.py import bkg, rand_port, wait_port_listen
     s.close()
 
 
+def remote_dev_steer(cfg, mode):
+    """Set vc-steer-ena on the remote PSP device"""
+    send_with_ack(cfg, b'dev steer\0' + struct.pack('B', mode))
+
+
 def spi_xchg(s, rx):
     s.send(struct.pack('I', rx['spi']) + rx['key'])
     tx = s.recv(4 + len(rx['key']))
diff --git a/tools/testing/selftests/drivers/net/psp_steer.py b/tools/testing/selftests/drivers/net/psp_steer.py
new file mode 100644
index 000000000000..0401a7c359e7
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/psp_steer.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Test suite for PSP virtualization cookie based Rx queue steering."""
+
+import errno
+import os
+import socket
+
+from lib.py import defer
+from lib.py import ksft_run, ksft_exit
+from lib.py import ksft_eq, ksft_ge, ksft_in, ksft_ne, ksft_raises
+from lib.py import KsftSkipEx
+from lib.py import NetDrvEpEnv
+from lib.py import NetdevFamily, NlError, PSPFamily
+
+from psp_lib import close_conn, init_psp_dev, make_psp_conn, psp_txrx, \
+    remote_conn_steer, remote_dev_steer, spi_xchg
+from psp_lib import responder as psp_responder
+
+# Not exposed by the socket module
+_SO_INCOMING_NAPI_ID = 56
+
+_VC_TX = 1 << 0
+_VC_RX = 1 << 1
+_VC_BOTH = _VC_TX | _VC_RX
+_VC_SIZE = 8
+
+
+def _require_steer(cfg):
+    """Skip unless the device can do VC steering"""
+    init_psp_dev(cfg)
+
+    if 'vc-steer-cap' not in cfg.psp_info:
+        raise KsftSkipEx("Device does not support PSP VC steering")
+
+
+def _require_queues(cfg, cnt):
+    if cfg.rx_queue_cnt < cnt or cfg.tx_queue_cnt < cnt:
+        raise KsftSkipEx(f"Test needs at least {cnt} Rx and Tx queues")
+
+
+def _set_steer(cfg, mode):
+    """Set vc-steer-ena locally for the duration of the test case"""
+    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+    prev = dev['vc-steer-ena']
+
+    cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'vc-steer-ena': mode})
+    defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id, 'vc-steer-ena': prev})
+
+
+def _set_remote_steer(cfg, mode):
+    remote_dev_steer(cfg, mode)
+    defer(remote_dev_steer, cfg, 0)
+
+
+def _enable_steer(cfg, local=_VC_BOTH, remote=_VC_BOTH):
+    """Turn steering on at both ends for the duration of the test case"""
+    _set_steer(cfg, local)
+    if remote is not None:
+        _set_remote_steer(cfg, remote)
+
+
+def _mss(s):
+    return s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
+
+
+def _force_tx_queue(cfg, qid):
+    """Point XPS at a single Tx queue, so we know the flow's Tx queue
+
+    The queue we ask the peer to steer us to is taken from the Tx queue
+    the stack picks for the flow, so pinning XPS is what makes the
+    outcome predictable.
+    """
+    all_cpus = f'{(1 << os.cpu_count()) - 1:x}'
+    for i in range(cfg.tx_queue_cnt):
+        mask = all_cpus if i == qid else '0'
+        with open(f'/sys/class/net/{cfg.ifname}/queues/tx-{i}/xps_cpus',
+                  'w', encoding='ascii') as fp:
+            fp.write(mask)
+
+
+def _psp_conn(cfg):
+    """Open a PSP connection, whatever the device is configured for"""
+    s = make_psp_conn(cfg)
+
+    rx = cfg.pspnl.rx_assoc({'version': 0, 'dev-id': cfg.psp_dev_id,
+                             'sock-fd': s.fileno()})
+    tx = spi_xchg(s, rx['rx-key'])
+    cfg.pspnl.tx_assoc({'dev-id': cfg.psp_dev_id, 'version': 0,
+                        'tx-key': tx, 'sock-fd': s.fileno()})
+    return s
+
+
+def _settled_rx_queue(cfg, s, sent):
+    """Run traffic until the peer picked our request up, report the queue
+
+    The first exchange carries our request to the peer, the second comes
+    back already steered.
+    """
+    sent = psp_txrx(cfg, s, 1, sent)
+    sent = psp_txrx(cfg, s, 1, sent)
+
+    napi_id = s.getsockopt(socket.SOL_SOCKET, _SO_INCOMING_NAPI_ID)
+    ksft_ne(napi_id, 0, comment="socket saw no traffic?")
+    ksft_in(napi_id, cfg.napi2queue, comment="unknown NAPI id")
+    return cfg.napi2queue[napi_id], sent
+
+
+#
+# Test cases
+#
+
+def dev_feature_toggle(cfg):
+    """ Set each direction in turn, check it is reported back """
+    _require_steer(cfg)
+
+    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+    defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
+                              'vc-steer-ena': dev['vc-steer-ena']})
+
+    for mode in ({'tx'}, {'rx'}, {'tx', 'rx'}, set()):
+        cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'vc-steer-ena': mode})
+        dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+        ksft_eq(dev['vc-steer-ena'], mode)
+
+
+def dev_feature_tx_needs_no_cap(cfg):
+    """ Granting a peer's request must not depend on the device """
+    init_psp_dev(cfg)
+
+    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+    defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
+                              'vc-steer-ena': dev['vc-steer-ena']})
+
+    cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'vc-steer-ena': {'tx'}})
+    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+    ksft_eq(dev['vc-steer-ena'], {'tx'})
+
+
+def dev_feature_rx_needs_cap(cfg):
+    """ Steering our own Rx does need the device to play along """
+    init_psp_dev(cfg)
+
+    if 'vc-steer-cap' in cfg.psp_info:
+        raise KsftSkipEx("Device can steer, nothing to reject")
+
+    with ksft_raises(NlError) as cm:
+        cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'vc-steer-ena': {'rx'}})
+    ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
+
+
+def dev_feature_bad_value(cfg):
+    """ Only the two direction bits are valid """
+    _require_steer(cfg)
+
+    with ksft_raises(NlError) as cm:
+        cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'vc-steer-ena': 0xdeadbeef})
+    ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL)
+
+
+def data_mss_adjust(cfg):
+    """ The cookie is 8B of extra header, the MSS has to account for it """
+    _require_steer(cfg)
+
+    _set_steer(cfg, 0)
+    with _psp_conn(cfg) as s:
+        plain = _mss(s)
+        close_conn(cfg, s)
+
+    # Either direction puts a cookie in every header we send
+    for mode in (_VC_TX, _VC_RX, _VC_BOTH):
+        _set_steer(cfg, mode)
+        with _psp_conn(cfg) as s:
+            ksft_eq(plain - _mss(s), _VC_SIZE, comment=f"mode {mode}")
+            close_conn(cfg, s)
+
+
+def data_mss_sampled_at_assoc(cfg):
+    """ Turning steering on must not resize a live connection's header """
+    _require_steer(cfg)
+
+    _set_steer(cfg, 0)
+    with _psp_conn(cfg) as s:
+        before = _mss(s)
+        _set_steer(cfg, _VC_BOTH)
+        ksft_eq(_mss(s), before)
+        close_conn(cfg, s)
+
+
+def data_steer_follows_tx_queue(cfg):
+    """ Traffic must land on the Rx queue paired with our Tx queue """
+    _require_steer(cfg)
+    _require_queues(cfg, 3)
+    _enable_steer(cfg)
+
+    defer(_force_tx_queue, cfg, -1)
+
+    with _psp_conn(cfg) as s:
+        sent = 0
+        for qid in (1, 2):
+            _force_tx_queue(cfg, qid)
+            qid_seen, sent = _settled_rx_queue(cfg, s, sent)
+            ksft_eq(qid_seen, qid)
+
+        close_conn(cfg, s)
+
+
+def data_steer_one_sided(cfg):
+    """ We ask, the peer only grants: our Rx still gets steered """
+    _require_steer(cfg)
+    _require_queues(cfg, 2)
+    _enable_steer(cfg, local=_VC_RX, remote=_VC_TX)
+
+    defer(_force_tx_queue, cfg, -1)
+    _force_tx_queue(cfg, 1)
+
+    with _psp_conn(cfg) as s:
+        qid, _ = _settled_rx_queue(cfg, s, 0)
+        ksft_eq(qid, 1)
+        close_conn(cfg, s)
+
+
+def _queue_info(cfg):
+    """Map NAPI ids to Rx queue ids, and count the queues"""
+    netnl = NetdevFamily()
+    queues = netnl.queue_get({'ifindex': cfg.ifindex}, dump=True)
+
+    cfg.napi2queue = {}
+    cfg.rx_queue_cnt = 0
+    cfg.tx_queue_cnt = 0
+    for q in queues:
+        if q['type'] == 'rx':
+            cfg.rx_queue_cnt += 1
+            if 'napi-id' in q:
+                cfg.napi2queue[q['napi-id']] = q['id']
+        elif q['type'] == 'tx':
+            cfg.tx_queue_cnt += 1
+
+
+def main() -> None:
+    """ Ksft boiler plate main """
+
+    with NetDrvEpEnv(__file__, queue_count=4) as cfg:
+        cfg.pspnl = PSPFamily()
+        _queue_info(cfg)
+
+        with psp_responder(cfg):
+            ksft_run(globs=globals(),
+                     case_pfx={"dev_", "assoc_", "data_"},
+                     args=(cfg, ))
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()
-- 
2.55.0


  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 ` [RFC net-next 3/6] selftests: drv-net: psp: move the PSP test plumbing into psp_lib.py Jakub Kicinski
2026-08-22 22:55 ` Jakub Kicinski [this message]
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-5-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