[RFC net-next 4/6] selftests: drv-net: psp_steer: test PSP VC based queue steering
Jakub Kicinski <[email protected]>
| Newsgroups | org.kernel.vger.netdev |
|---|---|
| Message-ID | <[email protected]> |
Cover the netlink interface for PSP VC steering. Signed-off-by: Jakub Kicinski <[email protected]> --- 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