Netdev List
 help / color / mirror / Atom feed
From: "Björn Töpel" <bjorn@kernel.org>
To: Maciej Fijalkowski <maciej.fijalkowski@intel.com>,
	netdev@vger.kernel.org
Cc: bpf@vger.kernel.org, magnus.karlsson@intel.com,
	stfomichev@gmail.com, kuba@kernel.org, pabeni@redhat.com,
	tushar.vyavahare@intel.com, kerneljasonxing@gmail.com,
	Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Subject: Re: [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes
Date: Thu, 15 Oct 2026 11:04:20 +0200	[thread overview]
Message-ID: <871p9r72sr.fsf@all.your.base.are.belong.to.us> (raw)
In-Reply-To: <20261014184539.774708-11-maciej.fijalkowski@intel.com>

Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:

> Run the two endpoints of a test case as separate xskxceiver processes,
> so that a later patch can run them on two hosts. A process binds only
> the interface given with -i, takes its role from -e rx|tx and meets the
> other endpoint over a small TCP control channel (-p HOST -P PORT). The
> other side is represented by a shadow ifobject that mirrors the local
> capabilities and holds a loaded copy of the XDP skeleton, so the shared
> test code keeps addressing both sides while only the local interface is
> programmed. Once BIDIRECTIONAL swaps the directions, the local
> interface runs the shadow's programs.
>
> The pthreads, the barrier, test->fail and -lpthread go away. Each
> process runs the worker of its local ifobject, and the endpoints
> exchange three fixed-size messages:
>
> - READY, exchanged before each step, works as a barrier; TCP ordering
>   puts all progress of the previous step before it
> - PROGRESS carries the number of packets RX consumed, which TX
>   subtracts from its in-flight count so as not to overrun the RX UMEM
> - ABORT tells the other endpoint to stop waiting after a failure
>
> TX listens and RX connects. The listener says on stderr when it
> listens, so the launcher starts the connector then rather than polling
> for the port, which could also find a foreign listener. The listener
> waits 30s for the connector, and -P takes a port number. As with the
> control channels of other selftests, the hosts are trusted and the
> channel is not authenticated. Test selection, interface capabilities
> and verdicts stay with the launcher.
>
> test_xsk.py starts TX on a random control port on 127.0.0.1 and RX
> once TX listens, and kills TX and fails the case when it does not
> listen within 30s. The case passes when both endpoints pass, and is
> skipped when one skips and neither fails. Only the output of an
> endpoint that does not pass is logged, prefixed with its role.
> xsk_wait_listen(), which reads the TX stderr up to the listen line,
> joins the library helpers, and xsk_wait_endpoint_exit() logs that
> output along with the rest.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
>  tools/testing/selftests/net/lib/Makefile      |   3 +-
>  .../testing/selftests/net/lib/py/__init__.py  |   4 +-
>  tools/testing/selftests/net/lib/py/xsk.py     |  35 ++-
>  .../testing/selftests/net/lib/xsk/test_xsk.c  | 205 +++++++-----
>  .../testing/selftests/net/lib/xsk/test_xsk.h  |  23 +-
>  .../testing/selftests/net/lib/xsk/xsk_peer.c  | 293 ++++++++++++++++++
>  .../testing/selftests/net/lib/xsk/xsk_peer.h  |  19 ++
>  .../selftests/net/lib/xsk/xskxceiver.c        | 136 ++++++--
>  tools/testing/selftests/net/test_xsk.py       |  68 ++--
>  9 files changed, 633 insertions(+), 153 deletions(-)
>  create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.c
>  create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.h
>
> diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
> index 5c1612315062..49dac776202b 100644
> --- a/tools/testing/selftests/net/lib/Makefile
> +++ b/tools/testing/selftests/net/lib/Makefile
> @@ -38,13 +38,12 @@ include ../bpf.mk
>  XSK_DIR := xsk
>  XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
>  XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
> -XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c) \
> +XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c xsk_peer.c) \
>  	$(top_srcdir)/tools/lib/find_bit.c
>  XSK_HEADERS := $(wildcard $(XSK_DIR)/*.h)
>  
>  PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
>  XSK_LDLIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -lz
> -XSK_LDLIBS += -lpthread
>  
>  $(XSK_BPF_OBJ): $(XSK_DIR)/xsk_xdp_progs.bpf.c $(XSK_DIR)/xsk_xdp_common.h $(BPFOBJ)
>  	$(call msg,BPF_PROG,,$@)
> diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
> index 1a93ab8b1e60..a82bac8a6b47 100644
> --- a/tools/testing/selftests/net/lib/py/__init__.py
> +++ b/tools/testing/selftests/net/lib/py/__init__.py
> @@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
>      ctl_file_write, wait_file, tool, tc
>  from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
>  from .xsk import xsk_reserve_hugepages, xsk_release_hugepages, \
> -    xsk_setup_busy_poll, xsk_wait_endpoint_exit
> +    xsk_setup_busy_poll, xsk_wait_endpoint_exit, xsk_wait_listen
>  from .ynl import NlError, NlctrlFamily, YnlFamily, \
>      EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily
>  from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
> @@ -34,7 +34,7 @@ __all__ = ["KSRC",
>             "wait_port_listen", "ctl_file_write", "wait_file", "tool", "tc",
>             "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
>             "xsk_reserve_hugepages", "xsk_release_hugepages",
> -           "xsk_setup_busy_poll", "xsk_wait_endpoint_exit",
> +           "xsk_setup_busy_poll", "xsk_wait_endpoint_exit", "xsk_wait_listen",
>             "NetdevSim", "NetdevSimDev",
>             "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
>             "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
> diff --git a/tools/testing/selftests/net/lib/py/xsk.py b/tools/testing/selftests/net/lib/py/xsk.py
> index 50985bcb5b80..6f4ffc4786d2 100644
> --- a/tools/testing/selftests/net/lib/py/xsk.py
> +++ b/tools/testing/selftests/net/lib/py/xsk.py
> @@ -5,9 +5,10 @@ Helpers to run xskxceiver, the AF_XDP test program in net/lib/xsk.
>  """
>  
>  import subprocess
> +import time
>  
>  from .ksft import ksft_pr
> -from .utils import cmd, ctl_file_write
> +from .utils import cmd, ctl_file_write, fd_read_timeout
>  
>  NR_HUGEPAGES = 32
>  
> @@ -41,10 +42,32 @@ def xsk_setup_busy_poll(ifname):
>      ctl_file_write(f"{sysfs}/gro_flush_timeout", 200000)
>  
>  
> -def xsk_wait_endpoint_exit(endpoint, label, timeout):
> -    """Wait up to timeout seconds for xskxceiver to exit, kill it if it does
> -    not, and return its exit code. Log its output, a KTAP stream of its own,
> -    prefixed with label, only if it did not pass."""
> +def xsk_wait_listen(endpoint, port, timeout):
> +    """Wait up to timeout seconds for an xskxceiver endpoint to print on
> +    stderr that it listens on the control port. Unlike polling for the port,
> +    this never takes a foreign listener for it and costs no SSH login per
> +    try on a remote host. Return the stderr read so far and whether that
> +    line came."""
> +    line = f"Listening for XSK peer on port {port}\n".encode()
> +    fd = endpoint.proc.stderr.fileno()
> +    head = b""
> +    deadline = time.monotonic() + timeout
> +    while line not in head:
> +        try:
> +            data = fd_read_timeout(fd, max(deadline - time.monotonic(), 0))
> +        except TimeoutError:
> +            break
> +        if not data:
> +            break
> +        head += data
> +    return head.decode(), line in head
> +
> +
> +def xsk_wait_endpoint_exit(endpoint, label, timeout, head=""):
> +    """Wait up to timeout seconds for an xskxceiver endpoint to exit, kill it
> +    if it does not, and return its exit code. Log its output, a KTAP stream
> +    of its own, prefixed with label, only if it did not pass. head is the
> +    start of its stderr, if xsk_wait_listen() took it off the pipe."""
>      try:
>          endpoint.process(terminate=False, fail=False, timeout=timeout)
>      except subprocess.TimeoutExpired:
> @@ -52,7 +75,7 @@ def xsk_wait_endpoint_exit(endpoint, label, timeout):
>          endpoint.process(terminate=False, fail=False)
>          ksft_pr(f"killed after {timeout}s", line_pfx=f"{label}|")
>      if endpoint.ret:
> -        for out in (endpoint.stdout, endpoint.stderr):
> +        for out in (endpoint.stdout, head + endpoint.stderr):
>              if out:
>                  ksft_pr(out.rstrip(), line_pfx=f"{label}|")
>      return endpoint.ret
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index e4e0d4dfab5a..f67a32c0260a 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> @@ -7,7 +7,6 @@
>  #include <linux/mman.h>
>  #include <linux/netdev.h>
>  #include <poll.h>
> -#include <pthread.h>
>  #include <string.h>
>  #include <sys/mman.h>
>  #include <sys/socket.h>
> @@ -15,6 +14,7 @@
>  #include <unistd.h>
>  
>  #include "test_xsk.h"
> +#include "xsk_peer.h"
>  #include "xsk_xdp_common.h"
>  #include "xsk_xdp_progs.skel.h"
>  
> @@ -38,10 +38,37 @@
>  static const u8 g_mac[ETH_ALEN] = {0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
>  
>  bool opt_verbose;
> -pthread_barrier_t barr;
> -pthread_mutex_t pacing_mutex = PTHREAD_MUTEX_INITIALIZER;
>  
>  int pkts_in_flight;
> +static struct xsk_peer *ctrl_peer;
> +
> +void xsk_set_endpoint(struct xsk_peer *peer)
> +{
> +	ctrl_peer = peer;
> +}
> +
> +static int pacing_rx_progress(u32 pkts)
> +{
> +	int err = xsk_peer_rx_progress(ctrl_peer, pkts);
> +
> +	if (err)
> +		ksft_print_msg("RX control channel failed: %d (%s)\n", err,
> +			       strerror(-err));
> +	return err;
> +}
> +
> +static int pacing_tx_sync(void)
> +{
> +	int acked = xsk_peer_tx_sync(ctrl_peer);
> +
> +	if (acked < 0) {
> +		ksft_print_msg("TX control channel failed: %d (%s)\n", acked,
> +			       strerror(-acked));
> +		return acked;
> +	}
> +	pkts_in_flight -= acked;
> +	return 0;
> +}
>  
>  /* The payload is a word consisting of a packet sequence number in the upper
>   * 16-bits and a intra packet data sequence number in the lower 16 bits. So the 3rd packet's
> @@ -256,7 +283,8 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
>  		ifobj->xsk->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
>  	}
>  
> -	if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING))
> +	if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING) &&
> +	    ifobj_is_local(ifobj_tx))
>  		hw_ring_size_reset(ifobj_tx);
>  
>  	test->ifobj_tx = ifobj_tx;
> @@ -264,7 +292,6 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
>  	test->current_step = 0;
>  	test->total_steps = 1;
>  	test->nb_sockets = 1;
> -	test->fail = false;
>  	test->set_ring = false;
>  	test->adjust_tail = false;
>  	test->adjust_tail_support = false;
> @@ -332,26 +359,31 @@ static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *x
>  	test->xskmap_tx = xskmap_tx;
>  }
>  
> -static int test_spec_set_mtu(struct test_spec *test, int mtu)
> +static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, int mtu)
>  {
>  	int err;
>  
> -	if (test->ifobj_rx->mtu != mtu) {
> -		err = xsk_set_mtu(test->ifobj_rx->ifindex, mtu);
> +	if (ifobj_is_local(ifobj) && ifobj->mtu != mtu) {
> +		err = xsk_set_mtu(ifobj->ifindex, mtu);
>  		if (err)
>  			return err;
> -		test->ifobj_rx->mtu = mtu;
> -	}
> -	if (test->ifobj_tx->mtu != mtu) {
> -		err = xsk_set_mtu(test->ifobj_tx->ifindex, mtu);
> -		if (err)
> -			return err;
> -		test->ifobj_tx->mtu = mtu;
>  	}
>  
> +	ifobj->mtu = mtu;
>  	return 0;
>  }
>  
> +static int test_spec_set_mtu(struct test_spec *test, int mtu)
> +{
> +	int err;
> +
> +	err = test_spec_set_mtu_ifobj(test->ifobj_rx, mtu);
> +	if (err)
> +		return err;
> +
> +	return test_spec_set_mtu_ifobj(test->ifobj_tx, mtu);
> +}
> +
>  void pkt_stream_reset(struct pkt_stream *pkt_stream)
>  {
>  	if (pkt_stream) {
> @@ -1001,6 +1033,9 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
>  	fds.fd = xsk_socket__fd(xsk->xsk);
>  	fds.events = POLLIN;
>  
> +	if (pacing_rx_progress(0))
> +		return TEST_FAILURE;
> +
>  	ret = kick_rx(xsk);
>  	if (ret)
>  		return TEST_FAILURE;
> @@ -1100,9 +1135,8 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
>  	if (ifobj->release_rx)
>  		xsk_ring_cons__release(&xsk->rx, frags_processed);
>  
> -	pthread_mutex_lock(&pacing_mutex);
> -	pkts_in_flight -= pkts_sent;
> -	pthread_mutex_unlock(&pacing_mutex);
> +	if (pacing_rx_progress(pkts_sent))
> +		return TEST_FAILURE;
>  	pkts_sent = 0;
>  
>  	return TEST_CONTINUE;
> @@ -1178,9 +1212,18 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
>  	int ret;
>  
>  	buffer_len = pkt_get_buffer_len(umem, pkt_stream->max_pkt_len);
> +	if (pacing_tx_sync())
> +		return TEST_FAILURE;
> +
>  	/* pkts_in_flight might be negative if many invalid packets are sent */
>  	if (pkts_in_flight >= (int)((umem_size(umem) - xsk->batch_size * buffer_len) /
>  	    buffer_len) && !test_timeout) {
> +		/* Only RX progress reports can lower pkts_in_flight. */
> +		if (xsk_peer_closed(ctrl_peer)) {
> +			ksft_print_msg("ERROR: [%s] RX exited with %d packets in flight\n",
> +				       __func__, pkts_in_flight);
> +			return TEST_FAILURE;
> +		}
>  		ret = kick_tx(xsk);
>  		if (ret)
>  			return TEST_FAILURE;
> @@ -1262,9 +1305,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
>  		valid_frags += nb_frags;
>  	}
>  
> -	pthread_mutex_lock(&pacing_mutex);
>  	pkts_in_flight += valid_pkts;
> -	pthread_mutex_unlock(&pacing_mutex);
>  
>  	xsk_ring_prod__submit(&xsk->tx, i);
>  	xsk->outstanding_tx += valid_frags;
> @@ -1341,9 +1382,6 @@ static int send_pkts(struct test_spec *test, struct ifobject *ifobject)
>  			if (ret != TEST_CONTINUE)
>  				return ret;
>  
> -			if (test->fail)
> -				return TEST_FAILURE;
> -
>  			if (!test->poll_tmout) {
>  				ret = wait_for_tx_completion(&ifobject->xsk_arr[i]);
>  				if (ret)
> @@ -1665,36 +1703,57 @@ static int testapp_validate_rx_endpoint(struct test_spec *test,
>  	return err;
>  }
>  
> -void *worker_testapp_validate_tx(void *arg)
> +static int sync_endpoints(struct test_spec *test)
> +{
> +	int err = xsk_peer_ready(ctrl_peer);
> +
> +	if (err)
> +		ksft_print_msg("Endpoint synchronization failed at step %u: %d (%s)\n",
> +			       test->current_step, err, strerror(-err));
> +	return err;
> +}
> +
> +int worker_testapp_validate_tx(struct test_spec *test)
>  {
> -	struct test_spec *test = (struct test_spec *)arg;
>  	int err;
>  
> -	err = testapp_validate_tx_endpoint(test, test->ifobj_tx);
> +	err = sync_endpoints(test);
>  	if (err)
> -		test->fail = true;
> +		return err;
>  
> -	pthread_exit(NULL);
> +	err = testapp_validate_tx_endpoint(test, test->ifobj_tx);
> +	if (err) {
> +		ksft_print_msg("TX endpoint traffic failed at step %u: %d\n",
> +			       test->current_step, err);
> +		xsk_peer_abort(ctrl_peer);
> +	}
> +	return err;
>  }
>  
> -void *worker_testapp_validate_rx(void *arg)
> +int worker_testapp_validate_rx(struct test_spec *test)
>  {
> -	struct test_spec *test = (struct test_spec *)arg;
>  	struct ifobject *ifobject = test->ifobj_rx;
>  	int err;
>  
>  	err = testapp_prepare_rx_endpoint(test, ifobject);
> +	if (err) {
> +		ksft_print_msg("RX endpoint setup failed at step %u: %d\n",
> +			       test->current_step, err);
> +		xsk_peer_abort(ctrl_peer);
> +		return err;
> +	}
>  
> -	if (test->use_barrier)
> -		pthread_barrier_wait(&barr);
> -
> -	/* We leave only now in case of error to avoid getting stuck in the barrier */
> -	if (!err)
> -		err = testapp_validate_rx_endpoint(test, ifobject);
> +	err = sync_endpoints(test);
>  	if (err)
> -		test->fail = true;
> +		return err;
>  
> -	pthread_exit(NULL);
> +	err = testapp_validate_rx_endpoint(test, ifobject);
> +	if (err) {
> +		ksft_print_msg("RX endpoint traffic failed at step %u: %d\n",
> +			       test->current_step, err);
> +		xsk_peer_abort(ctrl_peer);
> +	}
> +	return err;
>  }
>  
>  static void testapp_clean_xsk_umem(struct ifobject *ifobj)
> @@ -1749,13 +1808,13 @@ static int xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_r
>  {
>  	int err = 0;
>  
> -	if (xdp_prog_changed_rx(test)) {
> +	if (ifobj_is_local(ifobj_rx) && xdp_prog_changed_rx(test)) {
>  		err = xsk_reattach_xdp(ifobj_rx, test->xdp_prog_rx, test->xskmap_rx, test->mode);
>  		if (err)
>  			return err;
>  	}
>  
> -	if (!ifobj_tx)
> +	if (!ifobj_tx || !ifobj_is_local(ifobj_tx))
>  		return 0;
>  
>  	if (xdp_prog_changed_tx(test))
> @@ -1768,7 +1827,7 @@ static void clean_sockets(struct test_spec *test, struct ifobject *ifobj)
>  {
>  	u32 i;
>  
> -	if (!ifobj || !test)
> +	if (!ifobj || !test || !ifobj_is_local(ifobj))
>  		return;
>  
>  	for (i = 0; i < test->nb_sockets; i++)
> @@ -1780,15 +1839,15 @@ static void clean_umem(struct test_spec *test, struct ifobject *ifobj1, struct i
>  	if (!ifobj1)
>  		return;
>  
> -	testapp_clean_xsk_umem(ifobj1);
> -	if (ifobj2)
> +	if (ifobj_is_local(ifobj1))
> +		testapp_clean_xsk_umem(ifobj1);
> +	if (ifobj2 && ifobj_is_local(ifobj2))
>  		testapp_clean_xsk_umem(ifobj2);
>  }
>  
>  static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *ifobj1,
>  				      struct ifobject *ifobj2)
>  {
> -	pthread_t t0, t1;
>  	u32 mbuf_cap;
>  	int err;
>  
> @@ -1819,51 +1878,27 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i
>  			       err, strerror(-err));
>  		return TEST_FAILURE;
>  	}
> -	test->use_barrier = !!ifobj2;
> -
> -	if (test->use_barrier) {
> -		if (pthread_barrier_init(&barr, NULL, 2))
> -			return TEST_FAILURE;
>  
> +	if (ifobj2)
>  		pkt_stream_reset(ifobj2->xsk->pkt_stream);
> -	}
> -
>  	test->current_step++;
>  	pkt_stream_reset(ifobj1->xsk->pkt_stream);
>  	pkts_in_flight = 0;
>  
> -	/*Spawn RX thread */
> -	pthread_create(&t0, NULL, ifobj1->func_ptr, test);
> -
> -	if (test->use_barrier) {
> -		pthread_barrier_wait(&barr);
> -		if (pthread_barrier_destroy(&barr)) {
> -			test->use_barrier = false;
> -			pthread_join(t0, NULL);
> -			clean_sockets(test, ifobj1);
> -			clean_umem(test, ifobj1, NULL);
> -			return TEST_FAILURE;
> -		}
> -	}
> -
> -	if (ifobj2) {
> -		/*Spawn TX thread */
> -		pthread_create(&t1, NULL, ifobj2->func_ptr, test);
> -		pthread_join(t1, NULL);
> -	}
> -
> -	pthread_join(t0, NULL);
> -
> -	if (test->total_steps == test->current_step || test->fail) {
> +	/* In a one-sided step, the idle endpoint only keeps in step. */
> +	if (ifobj_is_local(ifobj1))
> +		err = ifobj1->func_ptr(test);
> +	else if (ifobj2 && ifobj_is_local(ifobj2))
> +		err = ifobj2->func_ptr(test);
> +	else
> +		err = sync_endpoints(test);
> +	if (test->total_steps == test->current_step || err) {
>  		clean_sockets(test, ifobj1);
>  		clean_sockets(test, ifobj2);
>  		clean_umem(test, ifobj1, ifobj2);
>  	}
>  
> -	if (test->fail)
> -		return TEST_FAILURE;
> -
> -	return TEST_PASS;
> +	return err ? TEST_FAILURE : TEST_PASS;
>  }
>  
>  static int testapp_validate_traffic(struct test_spec *test)
> @@ -1879,7 +1914,8 @@ static int testapp_validate_traffic(struct test_spec *test)
>  
>  	if (test->set_ring) {
>  		if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING)) {
> -			if (set_ring_size(ifobj_tx)) {
> +			if (ifobj_is_local(ifobj_tx) &&
> +			    set_ring_size(ifobj_tx)) {
>  				ksft_print_msg("Failed to change HW ring size.\n");
>  				return TEST_FAILURE;
>  			}
> @@ -1912,7 +1948,7 @@ int testapp_teardown(struct test_spec *test)
>  
>  static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2)
>  {
> -	thread_func_t tmp_func_ptr = (*ifobj1)->func_ptr;
> +	test_func_t tmp_func_ptr = (*ifobj1)->func_ptr;
>  	struct ifobject *tmp_ifobj = (*ifobj1);
>  
>  	(*ifobj1)->func_ptr = (*ifobj2)->func_ptr;
> @@ -1951,6 +1987,9 @@ static int swap_xsk_resources(struct test_spec *test)
>  	test->ifobj_tx->xsk = &test->ifobj_tx->xsk_arr[1];
>  	test->ifobj_rx->xsk = &test->ifobj_rx->xsk_arr[1];
>  
> +	if (!ifobj_is_local(test->ifobj_rx))
> +		return TEST_PASS;
> +
>  	ret = xsk_update_xskmap(test->ifobj_rx->xskmap, test->ifobj_rx->xsk->xsk, 0);
>  	if (ret)
>  		return TEST_FAILURE;
> @@ -2299,7 +2338,7 @@ int testapp_too_many_frags(struct test_spec *test)
>  	return ret;
>  }
>  
> -static int xsk_load_xdp_programs(struct ifobject *ifobj)
> +int xsk_load_xdp_programs(struct ifobject *ifobj)
>  {
>  	ifobj->xdp_progs = xsk_xdp_progs__open_and_load();
>  	if (libbpf_get_error(ifobj->xdp_progs))
> @@ -2357,12 +2396,10 @@ static int detect_ifobj_caps(struct ifobject *ifobj)
>  	return 0;
>  }
>  
> -int init_iface(struct ifobject *ifobj, thread_func_t func_ptr)
> +int init_iface(struct ifobject *ifobj)
>  {
>  	int err;
>  
> -	ifobj->func_ptr = func_ptr;
> -
>  	err = xsk_load_xdp_programs(ifobj);
>  	if (err) {
>  		ksft_print_msg("Error loading XDP program\n");
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> index 861a2ee3b8e1..53e5032510a0 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> @@ -77,9 +77,13 @@ enum test_mode {
>  struct ifobject;
>  struct test_spec;
>  typedef int (*validation_func_t)(struct ifobject *ifobj);
> -typedef void *(*thread_func_t)(void *arg);
>  typedef int (*test_func_t)(struct test_spec *test);
>  
> +struct xsk_peer;
> +
> +/* The control channel to the other endpoint. */
> +void xsk_set_endpoint(struct xsk_peer *peer);
> +
>  struct xsk_socket_info {
>  	struct xsk_ring_cons rx;
>  	struct xsk_ring_prod tx;
> @@ -139,7 +143,7 @@ struct ifobject {
>  	struct xsk_caps caps;
>  	struct xsk_socket_info *xsk;
>  	struct xsk_socket_info *xsk_arr;
> -	thread_func_t func_ptr;
> +	test_func_t func_ptr;
>  	validation_func_t validation_func;
>  	struct xsk_xdp_progs *xdp_progs;
>  	struct bpf_map *xskmap;
> @@ -169,11 +173,18 @@ static inline void ifobj_set_cap(struct ifobject *ifobj, u32 cap)
>  	ifobj->caps.flags |= cap;
>  }
>  
> +/* The shadow of the peer's ifobject is never bound to an interface. */
> +static inline bool ifobj_is_local(const struct ifobject *ifobj)
> +{
> +	return ifobj->ifindex;
> +}
> +
>  #define xsk_get_cap(test, cap) ((test)->ifobj_tx->caps.cap)
>  
>  struct ifobject *ifobject_create(void);
>  void ifobject_delete(struct ifobject *ifobj);
> -int init_iface(struct ifobject *ifobj, thread_func_t func_ptr);
> +int init_iface(struct ifobject *ifobj);
> +int xsk_load_xdp_programs(struct ifobject *ifobj);
>  
>  int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, u64 size);
>  int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem,
> @@ -222,12 +233,10 @@ struct test_spec {
>  	u16 total_steps;
>  	u16 current_step;
>  	u16 nb_sockets;
> -	bool fail;
>  	bool set_ring;
>  	bool adjust_tail;
>  	bool adjust_tail_support;
>  	bool poll_tmout;
> -	bool use_barrier;
>  	enum test_mode mode;
>  	char name[MAX_TEST_NAME_SIZE];
>  };
> @@ -288,8 +297,8 @@ int testapp_xdp_metadata_mb(struct test_spec *test);
>  int testapp_xdp_prog_cleanup(struct test_spec *test);
>  int testapp_xdp_shared_umem(struct test_spec *test);
>  
> -void *worker_testapp_validate_rx(void *arg);
> -void *worker_testapp_validate_tx(void *arg);
> +int worker_testapp_validate_rx(struct test_spec *test);
> +int worker_testapp_validate_tx(struct test_spec *test);
>  
>  static const struct test_spec tests[] = {
>  	{.name = "SEND_RECEIVE", .test_func = testapp_send_receive},
> diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.c b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
> new file mode 100644
> index 000000000000..e3e353d6abd9
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
> @@ -0,0 +1,293 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <netdb.h>
> +#include <netinet/tcp.h>
> +#include <poll.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include "xsk_peer.h"
> +
> +#define XSK_PEER_MAGIC			0x58534b50
> +#define XSK_PEER_ACCEPT_TMOUT_MS	30000
> +
> +/*
> + * One case per connection; the launcher owns the verdict. As with the control
> + * channels of other selftests, the test hosts are trusted: nothing
> + * authenticates the peer.
> + */
> +enum xsk_peer_msg_type {
> +	XSK_PEER_READY = 1,
> +	XSK_PEER_PROGRESS,
> +	XSK_PEER_ABORT,
> +};
> +
> +struct xsk_peer_msg {
> +	u32 magic;
> +	u32 type;
> +	u32 pkts;
> +};
> +
> +struct xsk_peer {
> +	int fd;
> +	u32 acked;
> +	bool ready_seen;
> +	bool closed;
> +};
> +
> +static int io_full(int fd, void *buf, size_t len, bool write_op)
> +{
> +	size_t done = 0;
> +	ssize_t ret;
> +
> +	while (done < len) {
> +		if (write_op)
> +			ret = send(fd, (char *)buf + done, len - done, MSG_NOSIGNAL);
> +		else
> +			ret = read(fd, (char *)buf + done, len - done);
> +		if (ret < 0 && errno == EINTR)
> +			continue;
> +		if (ret < 0)
> +			return -errno;
> +		if (!ret)
> +			return -EPIPE;
> +		done += ret;
> +	}
> +	return 0;
> +}
> +
> +/*
> + * Past its last barrier the peer may exit at any time, and it resets the
> + * connection if our progress reports are still unread.
> + */
> +static bool peer_gone(int err)
> +{
> +	return err == -EPIPE || err == -ECONNRESET;
> +}
> +
> +static int peer_send(struct xsk_peer *peer, u32 type, u32 pkts)
> +{
> +	struct xsk_peer_msg msg = {
> +		.magic = htonl(XSK_PEER_MAGIC),
> +		.type = htonl(type),
> +		.pkts = htonl(pkts),
> +	};
> +
> +	return io_full(peer->fd, &msg, sizeof(msg), true);
> +}
> +
> +static int peer_recv_one(struct xsk_peer *peer)
> +{
> +	struct xsk_peer_msg msg;
> +	u32 pkts;
> +	int err;
> +
> +	err = io_full(peer->fd, &msg, sizeof(msg), false);
> +	if (peer_gone(err))
> +		peer->closed = true;
> +	if (err)
> +		return err;
> +	if (ntohl(msg.magic) != XSK_PEER_MAGIC)
> +		return -EPROTO;
> +
> +	switch (ntohl(msg.type)) {
> +	case XSK_PEER_READY:
> +		/* The peer waits for our READY, so it is at most one step ahead. */
> +		if (peer->ready_seen)
> +			return -EPROTO;
> +		peer->ready_seen = true;
> +		return 0;
> +	case XSK_PEER_PROGRESS:
> +		/* xsk_peer_tx_sync() returns the sum as an int. */
> +		pkts = ntohl(msg.pkts);
> +		if (pkts > INT_MAX - peer->acked)
> +			return -EPROTO;
> +		peer->acked += pkts;
> +		return 0;
> +	case XSK_PEER_ABORT:
> +		return -ECANCELED;
> +	default:
> +		return -EPROTO;
> +	}
> +}
> +
> +/*
> + * Consume what the peer has sent so far without blocking. A closed connection
> + * is not an error here, see peer_gone(); xsk_peer_closed() reports it.
> + */
> +static int peer_drain(struct xsk_peer *peer)
> +{
> +	struct pollfd pfd = { .fd = peer->fd, .events = POLLIN };
> +	int ret;
> +
> +	while (!peer->closed) {
> +		ret = poll(&pfd, 1, 0);
> +		if (ret < 0 && errno == EINTR)
> +			continue;
> +		if (ret < 0)
> +			return -errno;
> +		if (!ret)
> +			break;
> +		ret = peer_recv_one(peer);
> +		if (ret && !peer->closed)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +/* Return once both endpoints are set up for the next step. */
> +int xsk_peer_ready(struct xsk_peer *peer)
> +{
> +	int err;
> +
> +	err = peer_send(peer, XSK_PEER_READY, 0);
> +	while (!err && !peer->ready_seen)
> +		err = peer_recv_one(peer);
> +	if (err)
> +		return err;
> +
> +	/* TCP ordering puts all progress of the previous step before READY. */
> +	peer->ready_seen = false;
> +	peer->acked = 0;
> +	return 0;
> +}
> +
> +void xsk_peer_abort(struct xsk_peer *peer)
> +{
> +	if (peer && !peer->closed)
> +		peer_send(peer, XSK_PEER_ABORT, 0);
> +}
> +
> +int xsk_peer_rx_progress(struct xsk_peer *peer, u32 pkts)
> +{
> +	int err;
> +
> +	if (pkts && !peer->closed) {
> +		err = peer_send(peer, XSK_PEER_PROGRESS, pkts);
> +		/*
> +		 * Leave closed to peer_drain(), which first reads what the
> +		 * peer sent before it went away, such as an ABORT.
> +		 */
> +		if (err && !peer_gone(err))
> +			return err;
> +	}
> +	return peer_drain(peer);
> +}
> +
> +/* Return the packets RX reported consumed since the last call, or -errno. */
> +int xsk_peer_tx_sync(struct xsk_peer *peer)
> +{
> +	int err = peer_drain(peer);
> +	u32 acked = peer->acked;
> +
> +	if (err)
> +		return err;
> +	peer->acked = 0;
> +	return acked;
> +}
> +
> +bool xsk_peer_closed(struct xsk_peer *peer)
> +{
> +	return peer->closed;
> +}
> +
> +static int accept_tmout(int lfd)
> +{
> +	struct pollfd pfd = { .fd = lfd, .events = POLLIN };
> +	int ret;
> +
> +	do {
> +		ret = poll(&pfd, 1, XSK_PEER_ACCEPT_TMOUT_MS);
> +	} while (ret < 0 && errno == EINTR);
> +	if (!ret)
> +		errno = ETIMEDOUT;
> +	return ret > 0 ? accept(lfd, NULL, NULL) : -1;
> +}
> +
> +/*
> + * The listener says on stderr when it listens, and the launcher waits for
> + * that line before it starts the connector, so connect() needs no retry.
> + * accept() is bounded in case the connector exits before it gets that far.
> + */
> +static int peer_open_tcp(const char *host, const char *port, bool listen_side)
> +{
> +	struct addrinfo hints = {
> +		.ai_family = AF_UNSPEC,
> +		.ai_socktype = SOCK_STREAM,
> +		.ai_flags = AI_NUMERICSERV,
> +	};
> +	int fd = -1, lfd, saved = ECONNREFUSED, one = 1, err;
> +	struct addrinfo *res, *ai;
> +
> +	err = getaddrinfo(host, port, &hints, &res);
> +	if (err)
> +		return -EINVAL;
> +
> +	if (!listen_side)
> +		goto conn;
> +
> +	for (ai = res; ai; ai = ai->ai_next) {
> +		lfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
> +		if (lfd < 0)
> +			continue;
> +		setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
> +		if (!bind(lfd, ai->ai_addr, ai->ai_addrlen) && !listen(lfd, 1)) {
> +			fprintf(stderr, "Listening for XSK peer on port %s\n", port);
> +			fd = accept_tmout(lfd);
> +			saved = errno;
> +			close(lfd);
> +			break;
> +		}
> +		saved = errno;
> +		close(lfd);
> +	}
> +	freeaddrinfo(res);
> +	return fd >= 0 ? fd : -saved;
> +
> +conn:
> +	for (ai = res; ai; ai = ai->ai_next) {
> +		fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
> +		if (fd < 0)
> +			continue;
> +		if (!connect(fd, ai->ai_addr, ai->ai_addrlen))
> +			break;
> +		saved = errno;
> +		close(fd);
> +		fd = -1;
> +	}
> +	freeaddrinfo(res);
> +	return fd >= 0 ? fd : -saved;
> +}
> +
> +struct xsk_peer *xsk_peer_open(const char *host, const char *port,
> +			       bool listen_side)
> +{
> +	int fd = peer_open_tcp(host, port, listen_side);
> +	struct xsk_peer *peer;
> +	int one = 1;
> +
> +	if (fd < 0) {
> +		errno = -fd;
> +		return NULL;
> +	}
> +	peer = calloc(1, sizeof(*peer));
> +	if (!peer) {
> +		close(fd);
> +		return NULL;
> +	}
> +	peer->fd = fd;
> +	setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
> +	return peer;
> +}
> +
> +void xsk_peer_close(struct xsk_peer *peer)
> +{
> +	if (peer) {
> +		close(peer->fd);
> +		free(peer);
> +	}
> +}
> diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.h b/tools/testing/selftests/net/lib/xsk/xsk_peer.h
> new file mode 100644
> index 000000000000..da4462192994
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef XSK_PEER_H_
> +#define XSK_PEER_H_
> +
> +#include <linux/types.h>
> +
> +struct xsk_peer;
> +
> +struct xsk_peer *xsk_peer_open(const char *host, const char *port,
> +			       bool listen_side);
> +void xsk_peer_close(struct xsk_peer *peer);
> +
> +int xsk_peer_ready(struct xsk_peer *peer);
> +void xsk_peer_abort(struct xsk_peer *peer);
> +int xsk_peer_rx_progress(struct xsk_peer *peer, u32 pkts);
> +int xsk_peer_tx_sync(struct xsk_peer *peer);
> +bool xsk_peer_closed(struct xsk_peer *peer);
> +
> +#endif /* XSK_PEER_H_ */
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index 09ad623333be..769718cad8cd 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> @@ -9,9 +9,9 @@
>   * See test_xsk.py for detailed information on test topology
>   * and prerequisite network setup.
>   *
> - * This test program contains two threads, each thread is single socket with
> - * a unique UMEM. It validates in-order packet delivery and packet content
> - * by sending packets to each other.
> + * Each instance of this test program runs one endpoint, Tx or Rx, with a
> + * single socket and a unique UMEM. Two instances validate in-order packet
> + * delivery and packet content by sending packets to each other.
>   *
>   * Tests Information:
>   * ------------------
> @@ -57,12 +57,14 @@
>   *
>   * Flow:
>   * -----
> - * - Single process spawns two threads: Tx and Rx
> - * - Each of these two threads attach to a veth interface
> - * - Each thread creates one AF_XDP socket connected to a unique umem for each
> + * - test_xsk.py starts two processes: Tx and Rx
> + * - Each of these two processes attach to a veth interface
> + * - Each process creates one AF_XDP socket connected to a unique umem for each
>   *   veth interface
> - * - Tx thread Transmits a number of packets from veth<xxxx> to veth<yyyy>
> - * - Rx thread verifies if all packets were received and delivered in-order,
> + * - Tx process listens on a TCP control port, Rx process connects to it and
> + *   the two synchronize each test step over that connection
> + * - Tx process Transmits a number of packets from veth<xxxx> to veth<yyyy>
> + * - Rx process verifies if all packets were received and delivered in-order,
>   *   and have the right content
>   */
>  
> @@ -87,6 +89,7 @@
>  #include <sys/types.h>
>  
>  #include "test_xsk.h"
> +#include "xsk_peer.h"
>  #include "xsk_xdp_progs.skel.h"
>  #include "xsk.h"
>  #include "xskxceiver.h"
> @@ -95,8 +98,18 @@
>  #include "kselftest.h"
>  #include "xsk_xdp_common.h"
>  
> +enum xsk_endpoint_role {
> +	XSK_ENDPOINT_NONE,
> +	XSK_ENDPOINT_RX,
> +	XSK_ENDPOINT_TX,
> +};
> +
>  static enum test_mode opt_mode = TEST_MODE_ALL;
>  static u32 opt_run_test = RUN_ALL_TESTS;
> +static enum xsk_endpoint_role opt_endpoint_role = XSK_ENDPOINT_NONE;
> +static const char *opt_peer_host;
> +static const char *opt_peer_port;
> +static const char *opt_ifname;
>  
>  static void __exit_with_error(int error, const char *file, const char *func, int line)
>  {
> @@ -157,6 +170,9 @@ static struct option long_options[] = {
>  	{"mode", required_argument, 0, 'm'},
>  	{"list", no_argument, 0, 'l'},
>  	{"test", required_argument, 0, 't'},
> +	{"endpoint", required_argument, 0, 'e'},
> +	{"peer", required_argument, 0, 'p'},
> +	{"peer-port", required_argument, 0, 'P'},
>  	{"help", no_argument, 0, 'h'},
>  	{0, 0, 0, 0}
>  };
> @@ -164,7 +180,7 @@ static struct option long_options[] = {
>  static void print_usage(char **argv)
>  {
>  	const char *str =
> -		"  Usage: xskxceiver -i TX_IFACE -i RX_IFACE -m MODE -t TEST [OPTIONS]\n"
> +		"  Usage: xskxceiver -i IFACE -e rx|tx -m MODE -t TEST -p HOST -P PORT [OPTIONS]\n"
>  		"  Options:\n"
>  		"  -i, --interface      Use interface\n"
>  		"  -v, --verbose        Verbose output\n"
> @@ -172,16 +188,18 @@ static void print_usage(char **argv)
>  		"  -m, --mode           Mode to run: skb, drv or zc\n"
>  		"  -l, --list           List all available tests\n"
>  		"  -t, --test           Test case to run, a number from -l\n"
> +		"  -e, --endpoint       Role of this endpoint: rx or tx\n"
> +		"  -p, --peer           Control host: IPv4/IPv6 address or hostname\n"
> +		"  -P, --peer-port      Control TCP port (1-65535)\n"
>  		"  -h, --help           Display this help and exit\n";
>  
>  	ksft_print_msg(str, basename(argv[0]));
>  	ksft_exit_xfail();
>  }
>  
> -static void bind_iface(struct ifobject *ifobj, const char *ifname, thread_func_t func,
> -		       char **argv)
> +static void bind_iface(struct ifobject *ifobj, const char *ifname, char **argv)
>  {
> -	size_t len = ifname ? strlen(ifname) : 0;
> +	size_t len = strlen(ifname);
>  
>  	if (!len || len >= sizeof(ifobj->ifname))
>  		print_usage(argv);
> @@ -193,7 +211,7 @@ static void bind_iface(struct ifobject *ifobj, const char *ifname, thread_func_t
>  		ksft_exit_fail();
>  	}
>  
> -	if (init_iface(ifobj, func)) {
> +	if (init_iface(ifobj)) {
>  		ksft_print_msg("Error: cannot initialize interface %s\n", ifobj->ifname);
>  		ksft_exit_fail();
>  	}
> @@ -208,26 +226,33 @@ static void print_tests(void)
>  		printf("%u: %s\n", i, tests[i].name);
>  }
>  
> +static u32 parse_u32(const char *arg, u32 min, u32 max, char **argv)
> +{
> +	unsigned long val;
> +	char *end;
> +
> +	errno = 0;
> +	val = strtoul(arg, &end, 10);
> +	if (arg[0] < '0' || arg[0] > '9' || errno || *end || val < min || val > max)
> +		print_usage(argv);
> +	return val;
> +}
> +
>  static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx, int argc,
>  			       char **argv)
>  {
> -	const char *ifname[2] = {};
> -	u32 interface_nb = 0;
>  	int option_index, c;
>  
>  	opterr = 0;
>  
>  	for (;;) {
> -		c = getopt_long(argc, argv, "i:vbm:lt:", long_options, &option_index);
> +		c = getopt_long(argc, argv, "i:vbm:lt:e:p:P:", long_options, &option_index);
>  		if (c == -1)
>  			break;
>  
>  		switch (c) {
>  		case 'i':
> -			if (interface_nb >= ARRAY_SIZE(ifname))
> -				break;
> -
> -			ifname[interface_nb++] = optarg;
> +			opt_ifname = optarg;
>  			break;
>  		case 'v':
>  			opt_verbose = true;
> @@ -256,17 +281,31 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
>  			if (errno)
>  				print_usage(argv);
>  			break;
> +		case 'e':
> +			if (!strcmp(optarg, "rx"))
> +				opt_endpoint_role = XSK_ENDPOINT_RX;
> +			else if (!strcmp(optarg, "tx"))
> +				opt_endpoint_role = XSK_ENDPOINT_TX;
> +			else
> +				print_usage(argv);
> +			break;
> +		case 'p':
> +			opt_peer_host = optarg;
> +			break;
> +		case 'P':
> +			parse_u32(optarg, 1, UINT16_MAX, argv);

If you respin; This is useless. Remove this and the function. We'll
catch an incorrect port elsewhere.


Reviewed-by: Björn Töpel <bjorn@kernel.org>

  reply	other threads:[~2026-10-15  9:04 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-15  8:14   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
2026-10-15  8:15   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
2026-10-15  8:17   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
2026-10-15  8:18   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
2026-10-15  8:20   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
2026-10-15  8:22   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
2026-10-15  8:27   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
2026-10-15  8:29   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-15  8:34   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
2026-10-15  9:04   ` Björn Töpel [this message]
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
2026-10-15  9:42   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
2026-10-15 10:05   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-15 10:29   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski

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=871p9r72sr.fsf@all.your.base.are.belong.to.us \
    --to=bjorn@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=kerneljasonxing@gmail.com \
    --cc=kuba@kernel.org \
    --cc=maciej.fijalkowski@intel.com \
    --cc=magnus.karlsson@intel.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=stfomichev@gmail.com \
    --cc=tushar.vyavahare@intel.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