| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/09 20:57 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch only modifies user-space selftests and build files under tools/testing/selftests/drivers/net/hw/ and does not touch any kernel code or affect the compiled kernel image.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/09 20:57 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 39d46c81f125707e39015ddbc5d5617b69f4baa7\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Sep 9 20:57:25 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile\nindex bd3b8d2fa47e0..8aebdc6feb177 100644\n--- a/tools/testing/selftests/drivers/net/hw/Makefile\n+++ b/tools/testing/selftests/drivers/net/hw/Makefile\n@@ -18,10 +18,6 @@ else\n $(warning excluding iouring tests, liburing not installed or too old)\n endif\n \n-TEST_GEN_FILES := \\\n-\t$(COND_GEN_FILES) \\\n-# end of TEST_GEN_FILES\n-\n TEST_PROGS = \\\n \tcsum.py \\\n \tdevlink_rate_cross_esw.py \\\n@@ -80,6 +76,7 @@ TEST_INCLUDES := \\\n YNL_GEN_FILES := \\\n \tncdevmem \\\n \ttoeplitz \\\n+\t$(COND_GEN_FILES) \\\n # end of YNL_GEN_FILES\n TEST_GEN_FILES += $(YNL_GEN_FILES)\n TEST_GEN_FILES += $(patsubst %.c,%.o,$(wildcard *.bpf.c))\n@@ -97,5 +94,5 @@ include ../../../net/ynl.mk\n include ../../../net/bpf.mk\n \n ifeq ($(HAS_IOURING_ZCRX),y)\n-$(OUTPUT)/iou-zcrx: LDLIBS += -luring\n+$(OUTPUT)/iou-zcrx: LDLIBS += -luring -lpthread\n endif\ndiff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c\nindex f6a8fc5fac241..91b025a8771e1 100644\n--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c\n+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c\n@@ -4,6 +4,7 @@\n #include \u003cerror.h\u003e\n #include \u003cfcntl.h\u003e\n #include \u003climits.h\u003e\n+#include \u003cpthread.h\u003e\n #include \u003cstdbool.h\u003e\n #include \u003cstdint.h\u003e\n #include \u003cstdio.h\u003e\n@@ -37,6 +38,8 @@\n #include \u003csys/wait.h\u003e\n \n #include \u003cliburing.h\u003e\n+#include \u003cynl.h\u003e\n+#include \"netdev-user.h\"\n \n #define SKIP_CODE\t42\n \n@@ -85,16 +88,29 @@ static int cfg_send_size = SEND_SIZE;\n static struct sockaddr_in6 cfg_addr;\n static unsigned int cfg_rx_buf_len;\n static bool cfg_dry_run;\n+static int cfg_num_threads = 1;\n \n static char *payload;\n-static void *area_ptr;\n-static void *ring_ptr;\n-static size_t ring_size;\n-static struct io_uring_zcrx_rq rq_ring;\n-static unsigned long area_token;\n-static int connfd;\n-static bool stop;\n-static size_t received;\n+\n+#define MAX_CONNS_PER_THREAD\t64\n+\n+struct thread_ctx {\n+\tstruct io_uring\t\tring;\n+\tvoid\t\t\t*area_ptr;\n+\tvoid\t\t\t*ring_ptr;\n+\tsize_t\t\t\tring_size;\n+\tstruct io_uring_zcrx_rq\trq_ring;\n+\tunsigned long\t\tarea_token;\n+\tint\t\t\tqueue_id;\n+\tint\t\t\tnapi_id;\n+\tpthread_barrier_t\t*setup_done;\n+\tpthread_barrier_t\t*dispatch_done;\n+\n+\tint\t\t\tconnfds[MAX_CONNS_PER_THREAD];\n+\tsize_t\t\t\treceived[MAX_CONNS_PER_THREAD];\n+\tint\t\t\toneshot_recvs[MAX_CONNS_PER_THREAD];\n+\tint\t\t\tnr_conns;\n+};\n \n static unsigned long gettimeofday_ms(void)\n {\n@@ -132,16 +148,16 @@ static inline size_t get_refill_ring_size(unsigned int rq_entries)\n {\n \tsize_t size;\n \n-\tring_size = rq_entries * sizeof(struct io_uring_zcrx_rqe);\n+\tsize = rq_entries * sizeof(struct io_uring_zcrx_rqe);\n \t/* add space for the header (head/tail/etc.) */\n-\tring_size += page_size;\n-\treturn ALIGN_UP(ring_size, page_size);\n+\tsize += page_size;\n+\treturn ALIGN_UP(size, page_size);\n }\n \n-static void setup_zcrx(struct io_uring *ring)\n+static void setup_zcrx(struct thread_ctx *ctx)\n {\n+\tunsigned int rq_entries = AREA_SIZE / page_size;\n \tunsigned int ifindex;\n-\tunsigned int rq_entries = 4096;\n \tint ret;\n \n \tifindex = if_nametoindex(cfg_ifname);\n@@ -149,58 +165,58 @@ static void setup_zcrx(struct io_uring *ring)\n \t\terror(1, 0, \"bad interface name: %s\", cfg_ifname);\n \n \tif (cfg_rx_buf_len \u0026\u0026 cfg_rx_buf_len != page_size) {\n-\t\tarea_ptr = mmap(NULL,\n-\t\t\t\tAREA_SIZE,\n-\t\t\t\tPROT_READ | PROT_WRITE,\n-\t\t\t\tMAP_ANONYMOUS | MAP_PRIVATE |\n-\t\t\t\tMAP_HUGETLB | MAP_HUGE_2MB,\n-\t\t\t\t-1,\n-\t\t\t\t0);\n-\t\tif (area_ptr == MAP_FAILED) {\n+\t\tctx-\u003earea_ptr = mmap(NULL,\n+\t\t\t\t AREA_SIZE,\n+\t\t\t\t PROT_READ | PROT_WRITE,\n+\t\t\t\t MAP_ANONYMOUS | MAP_PRIVATE |\n+\t\t\t\t MAP_HUGETLB | MAP_HUGE_2MB,\n+\t\t\t\t -1,\n+\t\t\t\t 0);\n+\t\tif (ctx-\u003earea_ptr == MAP_FAILED) {\n \t\t\tprintf(\"Can't allocate huge pages\\n\");\n \t\t\texit(SKIP_CODE);\n \t\t}\n \t} else {\n-\t\tarea_ptr = mmap(NULL,\n-\t\t\t\tAREA_SIZE,\n-\t\t\t\tPROT_READ | PROT_WRITE,\n-\t\t\t\tMAP_ANONYMOUS | MAP_PRIVATE,\n-\t\t\t\t0,\n-\t\t\t\t0);\n-\t\tif (area_ptr == MAP_FAILED)\n+\t\tctx-\u003earea_ptr = mmap(NULL,\n+\t\t\t\t AREA_SIZE,\n+\t\t\t\t PROT_READ | PROT_WRITE,\n+\t\t\t\t MAP_ANONYMOUS | MAP_PRIVATE,\n+\t\t\t\t 0,\n+\t\t\t\t 0);\n+\t\tif (ctx-\u003earea_ptr == MAP_FAILED)\n \t\t\terror(1, 0, \"mmap(): zero copy area\");\n \t}\n \n-\tring_size = get_refill_ring_size(rq_entries);\n-\tring_ptr = mmap(NULL,\n-\t\t\tring_size,\n-\t\t\tPROT_READ | PROT_WRITE,\n-\t\t\tMAP_ANONYMOUS | MAP_PRIVATE,\n-\t\t\t0,\n-\t\t\t0);\n+\tctx-\u003ering_size = get_refill_ring_size(rq_entries);\n+\tctx-\u003ering_ptr = mmap(NULL,\n+\t\t\t ctx-\u003ering_size,\n+\t\t\t PROT_READ | PROT_WRITE,\n+\t\t\t MAP_ANONYMOUS | MAP_PRIVATE,\n+\t\t\t 0,\n+\t\t\t 0);\n \n \tstruct io_uring_region_desc region_reg = {\n-\t\t.size = ring_size,\n-\t\t.user_addr = (__u64)(unsigned long)ring_ptr,\n+\t\t.size = ctx-\u003ering_size,\n+\t\t.user_addr = (__u64)(unsigned long)ctx-\u003ering_ptr,\n \t\t.flags = IORING_MEM_REGION_TYPE_USER,\n \t};\n \n \tstruct io_uring_zcrx_area_reg area_reg = {\n-\t\t.addr = (__u64)(unsigned long)area_ptr,\n+\t\t.addr = (__u64)(unsigned long)ctx-\u003earea_ptr,\n \t\t.len = AREA_SIZE,\n \t\t.flags = 0,\n \t};\n \n \tstruct t_io_uring_zcrx_ifq_reg reg = {\n \t\t.if_idx = ifindex,\n-\t\t.if_rxq = cfg_queue_id,\n+\t\t.if_rxq = ctx-\u003equeue_id,\n \t\t.rq_entries = rq_entries,\n \t\t.area_ptr = (__u64)(unsigned long)\u0026area_reg,\n \t\t.region_ptr = (__u64)(unsigned long)\u0026region_reg,\n \t\t.rx_buf_len = cfg_rx_buf_len,\n \t};\n \n-\tret = io_uring_register_ifq(ring, (void *)\u0026reg);\n+\tret = io_uring_register_ifq(\u0026ctx-\u003ering, (void *)\u0026reg);\n \tif (cfg_rx_buf_len \u0026\u0026 (ret == -EINVAL || ret == -EOPNOTSUPP ||\n \t\t\t ret == -ERANGE)) {\n \t\tprintf(\"Large chunks are not supported %i\\n\", ret);\n@@ -209,74 +225,53 @@ static void setup_zcrx(struct io_uring *ring)\n \t\terror(1, 0, \"io_uring_register_ifq(): %d\", ret);\n \t}\n \n-\trq_ring.khead = (unsigned int *)((char *)ring_ptr + reg.offsets.head);\n-\trq_ring.ktail = (unsigned int *)((char *)ring_ptr + reg.offsets.tail);\n-\trq_ring.rqes = (struct io_uring_zcrx_rqe *)((char *)ring_ptr + reg.offsets.rqes);\n-\trq_ring.rq_tail = 0;\n-\trq_ring.ring_entries = reg.rq_entries;\n-\n-\tarea_token = area_reg.rq_area_token;\n-}\n-\n-static void add_accept(struct io_uring *ring, int sockfd)\n-{\n-\tstruct io_uring_sqe *sqe;\n-\n-\tsqe = io_uring_get_sqe(ring);\n+\tctx-\u003erq_ring.khead = (unsigned int *)((char *)ctx-\u003ering_ptr + reg.offsets.head);\n+\tctx-\u003erq_ring.ktail = (unsigned int *)((char *)ctx-\u003ering_ptr + reg.offsets.tail);\n+\tctx-\u003erq_ring.rqes = (struct io_uring_zcrx_rqe *)((char *)ctx-\u003ering_ptr + reg.offsets.rqes);\n+\tctx-\u003erq_ring.rq_tail = 0;\n+\tctx-\u003erq_ring.ring_entries = reg.rq_entries;\n \n-\tio_uring_prep_accept(sqe, sockfd, NULL, NULL, 0);\n-\tsqe-\u003euser_data = 1;\n+\tctx-\u003earea_token = area_reg.rq_area_token;\n }\n \n-static void add_recvzc(struct io_uring *ring, int sockfd)\n+static void add_recvzc(struct thread_ctx *ctx, int conn_idx)\n {\n \tstruct io_uring_sqe *sqe;\n \n-\tsqe = io_uring_get_sqe(ring);\n+\tsqe = io_uring_get_sqe(\u0026ctx-\u003ering);\n \n-\tio_uring_prep_rw(IORING_OP_RECV_ZC, sqe, sockfd, NULL, 0, 0);\n+\tio_uring_prep_rw(IORING_OP_RECV_ZC, sqe, ctx-\u003econnfds[conn_idx],\n+\t\t\t NULL, 0, 0);\n \tsqe-\u003eioprio |= IORING_RECV_MULTISHOT;\n-\tsqe-\u003euser_data = 2;\n+\tsqe-\u003euser_data = conn_idx;\n }\n \n-static void add_recvzc_oneshot(struct io_uring *ring, int sockfd, size_t len)\n+static void add_recvzc_oneshot(struct thread_ctx *ctx, int conn_idx, size_t len)\n {\n \tstruct io_uring_sqe *sqe;\n \n-\tsqe = io_uring_get_sqe(ring);\n+\tsqe = io_uring_get_sqe(\u0026ctx-\u003ering);\n \n-\tio_uring_prep_rw(IORING_OP_RECV_ZC, sqe, sockfd, NULL, len, 0);\n+\tio_uring_prep_rw(IORING_OP_RECV_ZC, sqe, ctx-\u003econnfds[conn_idx],\n+\t\t\t NULL, len, 0);\n \tsqe-\u003eioprio |= IORING_RECV_MULTISHOT;\n-\tsqe-\u003euser_data = 2;\n+\tsqe-\u003euser_data = conn_idx;\n }\n \n-static void process_accept(struct io_uring *ring, struct io_uring_cqe *cqe)\n+static void process_recvzc(struct thread_ctx *ctx, struct io_uring_cqe *cqe,\n+\t\t\t int conn_idx)\n {\n-\tif (cqe-\u003eres \u003c 0)\n-\t\terror(1, 0, \"accept()\");\n-\tif (connfd)\n-\t\terror(1, 0, \"Unexpected second connection\");\n-\n-\tconnfd = cqe-\u003eres;\n-\tif (cfg_oneshot)\n-\t\tadd_recvzc_oneshot(ring, connfd, page_size);\n-\telse\n-\t\tadd_recvzc(ring, connfd);\n-}\n-\n-static void process_recvzc(struct io_uring *ring, struct io_uring_cqe *cqe)\n-{\n-\tunsigned rq_mask = rq_ring.ring_entries - 1;\n+\tunsigned int rq_mask = ctx-\u003erq_ring.ring_entries - 1;\n \tstruct io_uring_zcrx_cqe *rcqe;\n \tstruct io_uring_zcrx_rqe *rqe;\n-\tstruct io_uring_sqe *sqe;\n \tuint64_t mask;\n \tchar *data;\n \tssize_t n;\n \tint i;\n \n-\tif (cqe-\u003eres == 0 \u0026\u0026 cqe-\u003eflags == 0 \u0026\u0026 cfg_oneshot_recvs == 0) {\n-\t\tstop = true;\n+\tif (cqe-\u003eres == 0 \u0026\u0026 cqe-\u003eflags == 0 \u0026\u0026\n+\t ctx-\u003eoneshot_recvs[conn_idx] == 0) {\n+\t\tctx-\u003enr_conns--;\n \t\treturn;\n \t}\n \n@@ -284,59 +279,170 @@ static void process_recvzc(struct io_uring *ring, struct io_uring_cqe *cqe)\n \t\terror(1, 0, \"recvzc(): %d\", cqe-\u003eres);\n \n \tif (cfg_oneshot) {\n-\t\tif (cqe-\u003eres == 0 \u0026\u0026 cqe-\u003eflags == 0 \u0026\u0026 cfg_oneshot_recvs) {\n-\t\t\tadd_recvzc_oneshot(ring, connfd, page_size);\n-\t\t\tcfg_oneshot_recvs--;\n+\t\tif (cqe-\u003eres == 0 \u0026\u0026 cqe-\u003eflags == 0 \u0026\u0026\n+\t\t ctx-\u003eoneshot_recvs[conn_idx]) {\n+\t\t\tadd_recvzc_oneshot(ctx, conn_idx, page_size);\n+\t\t\tctx-\u003eoneshot_recvs[conn_idx]--;\n+\t\t\treturn;\n \t\t}\n \t} else if (!(cqe-\u003eflags \u0026 IORING_CQE_F_MORE)) {\n-\t\tadd_recvzc(ring, connfd);\n+\t\tadd_recvzc(ctx, conn_idx);\n \t}\n \n \trcqe = (struct io_uring_zcrx_cqe *)(cqe + 1);\n \n \tn = cqe-\u003eres;\n \tmask = (1ULL \u003c\u003c IORING_ZCRX_AREA_SHIFT) - 1;\n-\tdata = (char *)area_ptr + (rcqe-\u003eoff \u0026 mask);\n+\tdata = (char *)ctx-\u003earea_ptr + (rcqe-\u003eoff \u0026 mask);\n \n \tfor (i = 0; i \u003c n; i++) {\n-\t\tif (*(data + i) != payload[(received + i)])\n+\t\tif (*(data + i) != payload[(ctx-\u003ereceived[conn_idx] + i)])\n \t\t\terror(1, 0, \"payload mismatch at %d\", i);\n \t}\n-\treceived += n;\n+\tctx-\u003ereceived[conn_idx] += n;\n \n-\trqe = \u0026rq_ring.rqes[(rq_ring.rq_tail \u0026 rq_mask)];\n-\trqe-\u003eoff = (rcqe-\u003eoff \u0026 ~IORING_ZCRX_AREA_MASK) | area_token;\n+\trqe = \u0026ctx-\u003erq_ring.rqes[(ctx-\u003erq_ring.rq_tail \u0026 rq_mask)];\n+\trqe-\u003eoff = (rcqe-\u003eoff \u0026 ~IORING_ZCRX_AREA_MASK) | ctx-\u003earea_token;\n \trqe-\u003elen = cqe-\u003eres;\n-\tio_uring_smp_store_release(rq_ring.ktail, ++rq_ring.rq_tail);\n+\tio_uring_smp_store_release(ctx-\u003erq_ring.ktail, ++ctx-\u003erq_ring.rq_tail);\n }\n \n-static void server_loop(struct io_uring *ring)\n+static void server_loop(struct thread_ctx *ctx)\n {\n \tstruct io_uring_cqe *cqe;\n \tunsigned int count = 0;\n \tunsigned int head;\n-\tint i, ret;\n \n-\tio_uring_submit_and_wait(ring, 1);\n+\tio_uring_submit_and_wait(\u0026ctx-\u003ering, 1);\n \n-\tio_uring_for_each_cqe(ring, head, cqe) {\n-\t\tif (cqe-\u003euser_data == 1)\n-\t\t\tprocess_accept(ring, cqe);\n-\t\telse if (cqe-\u003euser_data == 2)\n-\t\t\tprocess_recvzc(ring, cqe);\n-\t\telse\n-\t\t\terror(1, 0, \"unknown cqe\");\n+\tio_uring_for_each_cqe(\u0026ctx-\u003ering, head, cqe) {\n+\t\tprocess_recvzc(ctx, cqe, cqe-\u003euser_data);\n \t\tcount++;\n \t}\n-\tio_uring_cq_advance(ring, count);\n+\tio_uring_cq_advance(\u0026ctx-\u003ering, count);\n }\n \n-static void run_server(void)\n+static void *server_worker(void *arg)\n {\n-\tunsigned int flags = 0;\n-\tstruct io_uring ring;\n-\tint fd, enable, ret;\n+\tstruct io_uring_params params = { };\n+\tstruct thread_ctx *ctx = arg;\n \tuint64_t tstop;\n+\tint nr_conns;\n+\tint i;\n+\n+\tparams.flags |= IORING_SETUP_COOP_TASKRUN;\n+\tparams.flags |= IORING_SETUP_SINGLE_ISSUER;\n+\tparams.flags |= IORING_SETUP_DEFER_TASKRUN;\n+\tparams.flags |= IORING_SETUP_SUBMIT_ALL;\n+\tparams.flags |= IORING_SETUP_CQE32;\n+\tparams.flags |= IORING_SETUP_CQSIZE;\n+\tparams.cq_entries = AREA_SIZE / page_size;\n+\n+\tio_uring_queue_init_params(512, \u0026ctx-\u003ering, \u0026params);\n+\tsetup_zcrx(ctx);\n+\n+\tif (cfg_dry_run)\n+\t\treturn NULL;\n+\n+\tpthread_barrier_wait(ctx-\u003esetup_done);\n+\tpthread_barrier_wait(ctx-\u003edispatch_done);\n+\n+\tnr_conns = ctx-\u003enr_conns;\n+\n+\tfor (i = 0; i \u003c ctx-\u003enr_conns; i++) {\n+\t\tif (cfg_oneshot) {\n+\t\t\tctx-\u003eoneshot_recvs[i] = cfg_oneshot_recvs;\n+\t\t\tadd_recvzc_oneshot(ctx, i, page_size);\n+\t\t} else {\n+\t\t\tadd_recvzc(ctx, i);\n+\t\t}\n+\t}\n+\n+\ttstop = gettimeofday_ms() + 5000;\n+\twhile (ctx-\u003enr_conns \u003e 0 \u0026\u0026 gettimeofday_ms() \u003c tstop)\n+\t\tserver_loop(ctx);\n+\n+\tif (ctx-\u003enr_conns != 0)\n+\t\terror(1, 0, \"test failed: %d connections incomplete\",\n+\t\t ctx-\u003enr_conns);\n+\n+\tfor (i = 0; i \u003c nr_conns; i++) {\n+\t\tif (cfg_oneshot) {\n+\t\t\tif (!ctx-\u003ereceived[i])\n+\t\t\t\terror(1, 0, \"connection %d received no data\", i);\n+\t\t} else if (ctx-\u003ereceived[i] != (size_t)cfg_send_size) {\n+\t\t\terror(1, 0, \"connection %d received %zu of %d bytes\",\n+\t\t\t i, ctx-\u003ereceived[i], cfg_send_size);\n+\t\t}\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int query_napi_id(unsigned int ifindex, int queue_id)\n+{\n+\tstruct netdev_queue_get_req *req;\n+\tstruct netdev_queue_get_rsp *rsp;\n+\tstruct ynl_error yerr;\n+\tstruct ynl_sock *ys;\n+\tint napi_id;\n+\n+\tys = ynl_sock_create(\u0026ynl_netdev_family, \u0026yerr);\n+\tif (!ys)\n+\t\terror(1, 0, \"ynl_sock_create: %s\", yerr.msg);\n+\n+\treq = netdev_queue_get_req_alloc();\n+\tnetdev_queue_get_req_set_ifindex(req, ifindex);\n+\tnetdev_queue_get_req_set_type(req, NETDEV_QUEUE_TYPE_RX);\n+\tnetdev_queue_get_req_set_id(req, queue_id);\n+\n+\trsp = netdev_queue_get(ys, req);\n+\tif (!rsp)\n+\t\terror(1, 0, \"netdev_queue_get(q=%d): %s\", queue_id,\n+\t\t ys-\u003eerr.msg);\n+\tif (!rsp-\u003e_present.napi_id)\n+\t\terror(1, 0, \"netdev_queue_get(q=%d): napi_id not present\",\n+\t\t queue_id);\n+\n+\tnapi_id = rsp-\u003enapi_id;\n+\n+\tnetdev_queue_get_req_free(req);\n+\tnetdev_queue_get_rsp_free(rsp);\n+\tynl_sock_destroy(ys);\n+\n+\treturn napi_id;\n+}\n+\n+static int find_thread_by_conn(struct thread_ctx *ctxs, int connfd)\n+{\n+\tsocklen_t len = sizeof(int);\n+\tint napi_id, i;\n+\n+\tif (getsockopt(connfd, SOL_SOCKET, SO_INCOMING_NAPI_ID, \u0026napi_id, \u0026len))\n+\t\terror(1, errno, \"getsockopt(SO_INCOMING_NAPI_ID)\");\n+\n+\tfor (i = 0; i \u003c cfg_num_threads; i++) {\n+\t\tif (ctxs[i].napi_id == napi_id)\n+\t\t\treturn i;\n+\t}\n+\n+\terror(1, 0, \"unknown NAPI ID: %d\", napi_id);\n+\treturn -1;\n+}\n+\n+static void run_server(void)\n+{\n+\tpthread_barrier_t setup_done, dispatch_done;\n+\tint total_conns, accepted = 0, connfd;\n+\tstruct thread_ctx *ctxs;\n+\tint fd, ret, enable, i;\n+\tunsigned int ifindex;\n+\tpthread_t *threads;\n+\n+\tctxs = calloc(cfg_num_threads, sizeof(*ctxs));\n+\tthreads = calloc(cfg_num_threads, sizeof(*threads));\n+\tif (!ctxs || !threads)\n+\t\terror(1, 0, \"calloc()\");\n \n \tfd = socket(AF_INET6, SOCK_STREAM, 0);\n \tif (fd == -1)\n@@ -351,32 +457,71 @@ static void run_server(void)\n \tif (ret \u003c 0)\n \t\terror(1, 0, \"bind()\");\n \n-\tflags |= IORING_SETUP_COOP_TASKRUN;\n-\tflags |= IORING_SETUP_SINGLE_ISSUER;\n-\tflags |= IORING_SETUP_DEFER_TASKRUN;\n-\tflags |= IORING_SETUP_SUBMIT_ALL;\n-\tflags |= IORING_SETUP_CQE32;\n+\tpthread_barrier_init(\u0026setup_done, NULL, cfg_num_threads + 1);\n+\tpthread_barrier_init(\u0026dispatch_done, NULL, cfg_num_threads + 1);\n \n-\tio_uring_queue_init(512, \u0026ring, flags);\n+\tfor (i = 0; i \u003c cfg_num_threads; i++) {\n+\t\tctxs[i].queue_id = cfg_queue_id + i;\n+\t\tctxs[i].setup_done = \u0026setup_done;\n+\t\tctxs[i].dispatch_done = \u0026dispatch_done;\n+\t}\n+\n+\tfor (i = 0; i \u003c cfg_num_threads; i++) {\n+\t\tret = pthread_create(\u0026threads[i], NULL,\n+\t\t\t\t server_worker, \u0026ctxs[i]);\n+\t\tif (ret)\n+\t\t\terror(1, ret, \"pthread_create()\");\n+\t}\n \n-\tsetup_zcrx(\u0026ring);\n \tif (cfg_dry_run)\n-\t\treturn;\n+\t\tgoto join;\n+\n+\tpthread_barrier_wait(\u0026setup_done);\n \n \tif (listen(fd, 1024) \u003c 0)\n \t\terror(1, 0, \"listen()\");\n \n-\tadd_accept(\u0026ring, fd);\n+\tif (cfg_num_threads \u003e 1) {\n+\t\tifindex = if_nametoindex(cfg_ifname);\n+\t\tif (!ifindex)\n+\t\t\terror(1, 0, \"bad interface name: %s\", cfg_ifname);\n+\t\tfor (i = 0; i \u003c cfg_num_threads; i++)\n+\t\t\tctxs[i].napi_id = query_napi_id(ifindex,\n+\t\t\t\t\t\t\tctxs[i].queue_id);\n+\t}\n \n-\ttstop = gettimeofday_ms() + 5000;\n-\twhile (!stop \u0026\u0026 gettimeofday_ms() \u003c tstop)\n-\t\tserver_loop(\u0026ring);\n+\ttotal_conns = cfg_num_threads * cfg_num_threads;\n+\n+\twhile (accepted \u003c total_conns) {\n+\t\tint idx = 0;\n+\n+\t\tconnfd = accept(fd, NULL, NULL);\n+\t\tif (connfd \u003c 0)\n+\t\t\terror(1, errno, \"accept()\");\n+\n+\t\tif (cfg_num_threads \u003e 1)\n+\t\t\tidx = find_thread_by_conn(ctxs, connfd);\n \n-\tif (!stop)\n-\t\terror(1, 0, \"test failed\\n\");\n+\t\tif (ctxs[idx].nr_conns \u003e= MAX_CONNS_PER_THREAD)\n+\t\t\terror(1, 0, \"worker %d connection overflow\", idx);\n+\t\tctxs[idx].connfds[ctxs[idx].nr_conns++] = connfd;\n+\t\taccepted++;\n+\t}\n+\n+\tpthread_barrier_wait(\u0026dispatch_done);\n+\n+join:\n+\tfor (i = 0; i \u003c cfg_num_threads; i++)\n+\t\tpthread_join(threads[i], NULL);\n+\n+\tpthread_barrier_destroy(\u0026setup_done);\n+\tpthread_barrier_destroy(\u0026dispatch_done);\n+\tclose(fd);\n+\tfree(threads);\n+\tfree(ctxs);\n }\n \n-static void run_client(void)\n+static void *client_worker(void *arg)\n {\n \tssize_t to_send = cfg_send_size;\n \tssize_t sent = 0;\n@@ -402,12 +547,36 @@ static void run_client(void)\n \t}\n \n \tclose(fd);\n+\treturn NULL;\n+}\n+\n+static void run_client(void)\n+{\n+\tint total_conns = cfg_num_threads * cfg_num_threads;\n+\tpthread_t *threads;\n+\tint i, ret;\n+\n+\tthreads = calloc(total_conns, sizeof(*threads));\n+\tif (!threads)\n+\t\terror(1, 0, \"calloc()\");\n+\n+\tfor (i = 0; i \u003c total_conns; i++) {\n+\t\tret = pthread_create(\u0026threads[i], NULL, client_worker, NULL);\n+\t\tif (ret)\n+\t\t\terror(1, ret, \"pthread_create()\");\n+\t}\n+\n+\tfor (i = 0; i \u003c total_conns; i++)\n+\t\tpthread_join(threads[i], NULL);\n+\n+\tfree(threads);\n }\n \n static void usage(const char *filepath)\n {\n \terror(1, 0, \"Usage: %s (-4|-6) (-s|-c) -h\u003cserver_ip\u003e -p\u003cport\u003e \"\n-\t\t \"-l\u003cpayload_size\u003e -i\u003cifname\u003e -q\u003crxq_id\u003e\", filepath);\n+\t\t \"-l\u003cpayload_size\u003e -i\u003cifname\u003e -q\u003crxq_id\u003e -t\u003cnum_threads\u003e\",\n+\t\t filepath);\n }\n \n static void parse_opts(int argc, char **argv)\n@@ -425,7 +594,7 @@ static void parse_opts(int argc, char **argv)\n \t\tusage(argv[0]);\n \tcfg_payload_len = max_payload_len;\n \n-\twhile ((c = getopt(argc, argv, \"sch:p:l:i:q:o:z:x:d\")) != -1) {\n+\twhile ((c = getopt(argc, argv, \"sch:p:l:i:q:o:z:x:dt:\")) != -1) {\n \t\tswitch (c) {\n \t\tcase 's':\n \t\t\tif (cfg_client)\n@@ -466,6 +635,9 @@ static void parse_opts(int argc, char **argv)\n \t\tcase 'd':\n \t\t\tcfg_dry_run = true;\n \t\t\tbreak;\n+\t\tcase 't':\n+\t\t\tcfg_num_threads = strtoul(optarg, NULL, 0);\n+\t\t\tbreak;\n \t\t}\n \t}\n \ndiff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py\nindex b7a225fe4beae..c833535d8a03c 100755\n--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py\n+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py\n@@ -30,6 +30,13 @@ def create_rss_ctx(cfg):\n return int(values)\n \n \n+def create_rss_ctx_multi(cfg, start, count):\n+ \"\"\"Create an RSS context spanning count queues from start, return its ID.\"\"\"\n+ output = ethtool(f\"-X {cfg.ifname} context new start {start} equal {count}\").stdout\n+ values = re.search(r'New RSS context is (\\d+)', output).group(1)\n+ return int(values)\n+\n+\n def set_flow_rule(cfg):\n output = ethtool(f\"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.target}\").stdout\n values = re.search(r'ID (\\d+)', output).group(1)\n@@ -127,17 +134,56 @@ def _require_ntuple(cfg):\n defer(ethtool, f\"-K {cfg.ifname} ntuple-filters off\")\n \n \n+def rss_multiqueue(cfg):\n+ \"\"\"Steer the test flow to a multi-queue RSS context for multi-thread zcrx.\"\"\"\n+ channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})\n+ channels = channels['combined-count']\n+ if channels \u003c 3:\n+ raise KsftSkipEx('Test requires NETIF with at least 3 combined channels')\n+\n+ rings = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})\n+ rx_rings = rings['rx']\n+ hds_thresh = rings.get('hds-thresh', 0)\n+\n+ cfg.ethnl.rings_set({'header': {'dev-index': cfg.ifindex},\n+ 'tcp-data-split': 'enabled',\n+ 'hds-thresh': 0,\n+ 'rx': 64})\n+ defer(cfg.ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},\n+ 'tcp-data-split': 'unknown',\n+ 'hds-thresh': hds_thresh,\n+ 'rx': rx_rings})\n+ defer(mp_clear_wait, cfg)\n+\n+ cfg.num_threads = 2\n+ cfg.target = channels - cfg.num_threads\n+ ethtool(f\"-X {cfg.ifname} equal {cfg.target}\")\n+ defer(ethtool, f\"-X {cfg.ifname} default\")\n+\n+ rss_ctx_id = create_rss_ctx_multi(cfg, cfg.target, cfg.num_threads)\n+ defer(ethtool, f\"-X {cfg.ifname} delete context {rss_ctx_id}\")\n+\n+ flow_rule_id = set_flow_rule_rss(cfg, rss_ctx_id)\n+ defer(ethtool, f\"-N {cfg.ifname} delete {flow_rule_id}\")\n+\n+\n @ksft_variants([\n KsftNamedVariant(\"single\", single),\n KsftNamedVariant(\"rss\", rss),\n+ KsftNamedVariant(\"rss_multiqueue\", rss_multiqueue),\n ])\n def test_zcrx(cfg, setup) -\u003e None:\n cfg.require_ipver('6')\n _require_ntuple(cfg)\n \n+ cfg.num_threads = 1\n+\n setup(cfg)\n- rx_cmd = f\"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target}\"\n- tx_cmd = f\"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840\"\n+\n+ rx_cmd = (f\"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} \"\n+ f\"-q {cfg.target} -t {cfg.num_threads}\")\n+ tx_cmd = (f\"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} \"\n+ f\"-l 12840 -t {cfg.num_threads}\")\n with bkg(rx_cmd, exit_wait=True):\n wait_port_listen(cfg.port, proto=\"tcp\")\n cmd(tx_cmd, host=cfg.remote)\n"
} |
| 2/1 | 2026/09/09 20:57 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests and build files under tools/testing/selftests/drivers/net/hw/ and does not touch any kernel code or affect the compiled kernel image.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 39d46c81f125707e39015ddbc5d5617b69f4baa7
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 9 20:57:25 2026 +0000
syz-cluster: applied patch under review
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index bd3b8d2fa47e0..8aebdc6feb177 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -18,10 +18,6 @@ else
$(warning excluding iouring tests, liburing not installed or too old)
endif
-TEST_GEN_FILES := \
- $(COND_GEN_FILES) \
-# end of TEST_GEN_FILES
-
TEST_PROGS = \
csum.py \
devlink_rate_cross_esw.py \
@@ -80,6 +76,7 @@ TEST_INCLUDES := \
YNL_GEN_FILES := \
ncdevmem \
toeplitz \
+ $(COND_GEN_FILES) \
# end of YNL_GEN_FILES
TEST_GEN_FILES += $(YNL_GEN_FILES)
TEST_GEN_FILES += $(patsubst %.c,%.o,$(wildcard *.bpf.c))
@@ -97,5 +94,5 @@ include ../../../net/ynl.mk
include ../../../net/bpf.mk
ifeq ($(HAS_IOURING_ZCRX),y)
-$(OUTPUT)/iou-zcrx: LDLIBS += -luring
+$(OUTPUT)/iou-zcrx: LDLIBS += -luring -lpthread
endif
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
index f6a8fc5fac241..91b025a8771e1 100644
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
@@ -4,6 +4,7 @@
#include <error.h>
#include <fcntl.h>
#include <limits.h>
+#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@@ -37,6 +38,8 @@
#include <sys/wait.h>
#include <liburing.h>
+#include <ynl.h>
+#include "netdev-user.h"
#define SKIP_CODE 42
@@ -85,16 +88,29 @@ static int cfg_send_size = SEND_SIZE;
static struct sockaddr_in6 cfg_addr;
static unsigned int cfg_rx_buf_len;
static bool cfg_dry_run;
+static int cfg_num_threads = 1;
static char *payload;
-static void *area_ptr;
-static void *ring_ptr;
-static size_t ring_size;
-static struct io_uring_zcrx_rq rq_ring;
-static unsigned long area_token;
-static int connfd;
-static bool stop;
-static size_t received;
+
+#define MAX_CONNS_PER_THREAD 64
+
+struct thread_ctx {
+ struct io_uring ring;
+ void *area_ptr;
+ void *ring_ptr;
+ size_t ring_size;
+ struct io_uring_zcrx_rq rq_ring;
+ unsigned long area_token;
+ int queue_id;
+ int napi_id;
+ pthread_barrier_t *setup_done;
+ pthread_barrier_t *dispatch_done;
+
+ int connfds[MAX_CONNS_PER_THREAD];
+ size_t received[MAX_CONNS_PER_THREAD];
+ int oneshot_recvs[MAX_CONNS_PER_THREAD];
+ int nr_conns;
+};
static unsigned long gettimeofday_ms(void)
{
@@ -132,16 +148,16 @@ static inline size_t get_refill_ring_size(unsigned int rq_entries)
{
size_t size;
- ring_size = rq_entries * sizeof(struct io_uring_zcrx_rqe);
+ size = rq_entries * sizeof(struct io_uring_zcrx_rqe);
/* add space for the header (head/tail/etc.) */
- ring_size += page_size;
- return ALIGN_UP(ring_size, page_size);
+ size += page_size;
+ return ALIGN_UP(size, page_size);
}
-static void setup_zcrx(struct io_uring *ring)
+static void setup_zcrx(struct thread_ctx *ctx)
{
+ unsigned int rq_entries = AREA_SIZE / page_size;
unsigned int ifindex;
- unsigned int rq_entries = 4096;
int ret;
ifindex = if_nametoindex(cfg_ifname);
@@ -149,58 +165,58 @@ static void setup_zcrx(struct io_uring *ring)
error(1, 0, "bad interface name: %s", cfg_ifname);
if (cfg_rx_buf_len && cfg_rx_buf_len != page_size) {
- area_ptr = mmap(NULL,
- AREA_SIZE,
- PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE |
- MAP_HUGETLB | MAP_HUGE_2MB,
- -1,
- 0);
- if (area_ptr == MAP_FAILED) {
+ ctx->area_ptr = mmap(NULL,
+ AREA_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE |
+ MAP_HUGETLB | MAP_HUGE_2MB,
+ -1,
+ 0);
+ if (ctx->area_ptr == MAP_FAILED) {
printf("Can't allocate huge pages\n");
exit(SKIP_CODE);
}
} else {
- area_ptr = mmap(NULL,
- AREA_SIZE,
- PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE,
- 0,
- 0);
- if (area_ptr == MAP_FAILED)
+ ctx->area_ptr = mmap(NULL,
+ AREA_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ 0,
+ 0);
+ if (ctx->area_ptr == MAP_FAILED)
error(1, 0, "mmap(): zero copy area");
}
- ring_size = get_refill_ring_size(rq_entries);
- ring_ptr = mmap(NULL,
- ring_size,
- PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE,
- 0,
- 0);
+ ctx->ring_size = get_refill_ring_size(rq_entries);
+ ctx->ring_ptr = mmap(NULL,
+ ctx->ring_size,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ 0,
+ 0);
struct io_uring_region_desc region_reg = {
- .size = ring_size,
- .user_addr = (__u64)(unsigned long)ring_ptr,
+ .size = ctx->ring_size,
+ .user_addr = (__u64)(unsigned long)ctx->ring_ptr,
.flags = IORING_MEM_REGION_TYPE_USER,
};
struct io_uring_zcrx_area_reg area_reg = {
- .addr = (__u64)(unsigned long)area_ptr,
+ .addr = (__u64)(unsigned long)ctx->area_ptr,
.len = AREA_SIZE,
.flags = 0,
};
struct t_io_uring_zcrx_ifq_reg reg = {
.if_idx = ifindex,
- .if_rxq = cfg_queue_id,
+ .if_rxq = ctx->queue_id,
.rq_entries = rq_entries,
.area_ptr = (__u64)(unsigned long)&area_reg,
.region_ptr = (__u64)(unsigned long)®ion_reg,
.rx_buf_len = cfg_rx_buf_len,
};
- ret = io_uring_register_ifq(ring, (void *)®);
+ ret = io_uring_register_ifq(&ctx->ring, (void *)®);
if (cfg_rx_buf_len && (ret == -EINVAL || ret == -EOPNOTSUPP ||
ret == -ERANGE)) {
printf("Large chunks are not supported %i\n", ret);
@@ -209,74 +225,53 @@ static void setup_zcrx(struct io_uring *ring)
error(1, 0, "io_uring_register_ifq(): %d", ret);
}
- rq_ring.khead = (unsigned int *)((char *)ring_ptr + reg.offsets.head);
- rq_ring.ktail = (unsigned int *)((char *)ring_ptr + reg.offsets.tail);
- rq_ring.rqes = (struct io_uring_zcrx_rqe *)((char *)ring_ptr + reg.offsets.rqes);
- rq_ring.rq_tail = 0;
- rq_ring.ring_entries = reg.rq_entries;
-
- area_token = area_reg.rq_area_token;
-}
-
-static void add_accept(struct io_uring *ring, int sockfd)
-{
- struct io_uring_sqe *sqe;
-
- sqe = io_uring_get_sqe(ring);
+ ctx->rq_ring.khead = (unsigned int *)((char *)ctx->ring_ptr + reg.offsets.head);
+ ctx->rq_ring.ktail = (unsigned int *)((char *)ctx->ring_ptr + reg.offsets.tail);
+ ctx->rq_ring.rqes = (struct io_uring_zcrx_rqe *)((char *)ctx->ring_ptr + reg.offsets.rqes);
+ ctx->rq_ring.rq_tail = 0;
+ ctx->rq_ring.ring_entries = reg.rq_entries;
- io_uring_prep_accept(sqe, sockfd, NULL, NULL, 0);
- sqe->user_data = 1;
+ ctx->area_token = area_reg.rq_area_token;
}
-static void add_recvzc(struct io_uring *ring, int sockfd)
+static void add_recvzc(struct thread_ctx *ctx, int conn_idx)
{
struct io_uring_sqe *sqe;
- sqe = io_uring_get_sqe(ring);
+ sqe = io_uring_get_sqe(&ctx->ring);
- io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, sockfd, NULL, 0, 0);
+ io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, ctx->connfds[conn_idx],
+ NULL, 0, 0);
sqe->ioprio |= IORING_RECV_MULTISHOT;
- sqe->user_data = 2;
+ sqe->user_data = conn_idx;
}
-static void add_recvzc_oneshot(struct io_uring *ring, int sockfd, size_t len)
+static void add_recvzc_oneshot(struct thread_ctx *ctx, int conn_idx, size_t len)
{
struct io_uring_sqe *sqe;
- sqe = io_uring_get_sqe(ring);
+ sqe = io_uring_get_sqe(&ctx->ring);
- io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, sockfd, NULL, len, 0);
+ io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, ctx->connfds[conn_idx],
+ NULL, len, 0);
sqe->ioprio |= IORING_RECV_MULTISHOT;
- sqe->user_data = 2;
+ sqe->user_data = conn_idx;
}
-static void process_accept(struct io_uring *ring, struct io_uring_cqe *cqe)
+static void process_recvzc(struct thread_ctx *ctx, struct io_uring_cqe *cqe,
+ int conn_idx)
{
- if (cqe->res < 0)
- error(1, 0, "accept()");
- if (connfd)
- error(1, 0, "Unexpected second connection");
-
- connfd = cqe->res;
- if (cfg_oneshot)
- add_recvzc_oneshot(ring, connfd, page_size);
- else
- add_recvzc(ring, connfd);
-}
-
-static void process_recvzc(struct io_uring *ring, struct io_uring_cqe *cqe)
-{
- unsigned rq_mask = rq_ring.ring_entries - 1;
+ unsigned int rq_mask = ctx->rq_ring.ring_entries - 1;
struct io_uring_zcrx_cqe *rcqe;
struct io_uring_zcrx_rqe *rqe;
- struct io_uring_sqe *sqe;
uint64_t mask;
char *data;
ssize_t n;
int i;
- if (cqe->res == 0 && cqe->flags == 0 && cfg_oneshot_recvs == 0) {
- stop = true;
+ if (cqe->res == 0 && cqe->flags == 0 &&
+ ctx->oneshot_recvs[conn_idx] == 0) {
+ ctx->nr_conns--;
return;
}
@@ -284,59 +279,170 @@ static void process_recvzc(struct io_uring *ring, struct io_uring_cqe *cqe)
error(1, 0, "recvzc(): %d", cqe->res);
if (cfg_oneshot) {
- if (cqe->res == 0 && cqe->flags == 0 && cfg_oneshot_recvs) {
- add_recvzc_oneshot(ring, connfd, page_size);
- cfg_oneshot_recvs--;
+ if (cqe->res == 0 && cqe->flags == 0 &&
+ ctx->oneshot_recvs[conn_idx]) {
+ add_recvzc_oneshot(ctx, conn_idx, page_size);
+ ctx->oneshot_recvs[conn_idx]--;
+ return;
}
} else if (!(cqe->flags & IORING_CQE_F_MORE)) {
- add_recvzc(ring, connfd);
+ add_recvzc(ctx, conn_idx);
}
rcqe = (struct io_uring_zcrx_cqe *)(cqe + 1);
n = cqe->res;
mask = (1ULL << IORING_ZCRX_AREA_SHIFT) - 1;
- data = (char *)area_ptr + (rcqe->off & mask);
+ data = (char *)ctx->area_ptr + (rcqe->off & mask);
for (i = 0; i < n; i++) {
- if (*(data + i) != payload[(received + i)])
+ if (*(data + i) != payload[(ctx->received[conn_idx] + i)])
error(1, 0, "payload mismatch at %d", i);
}
- received += n;
+ ctx->received[conn_idx] += n;
- rqe = &rq_ring.rqes[(rq_ring.rq_tail & rq_mask)];
- rqe->off = (rcqe->off & ~IORING_ZCRX_AREA_MASK) | area_token;
+ rqe = &ctx->rq_ring.rqes[(ctx->rq_ring.rq_tail & rq_mask)];
+ rqe->off = (rcqe->off & ~IORING_ZCRX_AREA_MASK) | ctx->area_token;
rqe->len = cqe->res;
- io_uring_smp_store_release(rq_ring.ktail, ++rq_ring.rq_tail);
+ io_uring_smp_store_release(ctx->rq_ring.ktail, ++ctx->rq_ring.rq_tail);
}
-static void server_loop(struct io_uring *ring)
+static void server_loop(struct thread_ctx *ctx)
{
struct io_uring_cqe *cqe;
unsigned int count = 0;
unsigned int head;
- int i, ret;
- io_uring_submit_and_wait(ring, 1);
+ io_uring_submit_and_wait(&ctx->ring, 1);
- io_uring_for_each_cqe(ring, head, cqe) {
- if (cqe->user_data == 1)
- process_accept(ring, cqe);
- else if (cqe->user_data == 2)
- process_recvzc(ring, cqe);
- else
- error(1, 0, "unknown cqe");
+ io_uring_for_each_cqe(&ctx->ring, head, cqe) {
+ process_recvzc(ctx, cqe, cqe->user_data);
count++;
}
- io_uring_cq_advance(ring, count);
+ io_uring_cq_advance(&ctx->ring, count);
}
-static void run_server(void)
+static void *server_worker(void *arg)
{
- unsigned int flags = 0;
- struct io_uring ring;
- int fd, enable, ret;
+ struct io_uring_params params = { };
+ struct thread_ctx *ctx = arg;
uint64_t tstop;
+ int nr_conns;
+ int i;
+
+ params.flags |= IORING_SETUP_COOP_TASKRUN;
+ params.flags |= IORING_SETUP_SINGLE_ISSUER;
+ params.flags |= IORING_SETUP_DEFER_TASKRUN;
+ params.flags |= IORING_SETUP_SUBMIT_ALL;
+ params.flags |= IORING_SETUP_CQE32;
+ params.flags |= IORING_SETUP_CQSIZE;
+ params.cq_entries = AREA_SIZE / page_size;
+
+ io_uring_queue_init_params(512, &ctx->ring, ¶ms);
+ setup_zcrx(ctx);
+
+ if (cfg_dry_run)
+ return NULL;
+
+ pthread_barrier_wait(ctx->setup_done);
+ pthread_barrier_wait(ctx->dispatch_done);
+
+ nr_conns = ctx->nr_conns;
+
+ for (i = 0; i < ctx->nr_conns; i++) {
+ if (cfg_oneshot) {
+ ctx->oneshot_recvs[i] = cfg_oneshot_recvs;
+ add_recvzc_oneshot(ctx, i, page_size);
+ } else {
+ add_recvzc(ctx, i);
+ }
+ }
+
+ tstop = gettimeofday_ms() + 5000;
+ while (ctx->nr_conns > 0 && gettimeofday_ms() < tstop)
+ server_loop(ctx);
+
+ if (ctx->nr_conns != 0)
+ error(1, 0, "test failed: %d connections incomplete",
+ ctx->nr_conns);
+
+ for (i = 0; i < nr_conns; i++) {
+ if (cfg_oneshot) {
+ if (!ctx->received[i])
+ error(1, 0, "connection %d received no data", i);
+ } else if (ctx->received[i] != (size_t)cfg_send_size) {
+ error(1, 0, "connection %d received %zu of %d bytes",
+ i, ctx->received[i], cfg_send_size);
+ }
+ }
+
+ return NULL;
+}
+
+static int query_napi_id(unsigned int ifindex, int queue_id)
+{
+ struct netdev_queue_get_req *req;
+ struct netdev_queue_get_rsp *rsp;
+ struct ynl_error yerr;
+ struct ynl_sock *ys;
+ int napi_id;
+
+ ys = ynl_sock_create(&ynl_netdev_family, &yerr);
+ if (!ys)
+ error(1, 0, "ynl_sock_create: %s", yerr.msg);
+
+ req = netdev_queue_get_req_alloc();
+ netdev_queue_get_req_set_ifindex(req, ifindex);
+ netdev_queue_get_req_set_type(req, NETDEV_QUEUE_TYPE_RX);
+ netdev_queue_get_req_set_id(req, queue_id);
+
+ rsp = netdev_queue_get(ys, req);
+ if (!rsp)
+ error(1, 0, "netdev_queue_get(q=%d): %s", queue_id,
+ ys->err.msg);
+ if (!rsp->_present.napi_id)
+ error(1, 0, "netdev_queue_get(q=%d): napi_id not present",
+ queue_id);
+
+ napi_id = rsp->napi_id;
+
+ netdev_queue_get_req_free(req);
+ netdev_queue_get_rsp_free(rsp);
+ ynl_sock_destroy(ys);
+
+ return napi_id;
+}
+
+static int find_thread_by_conn(struct thread_ctx *ctxs, int connfd)
+{
+ socklen_t len = sizeof(int);
+ int napi_id, i;
+
+ if (getsockopt(connfd, SOL_SOCKET, SO_INCOMING_NAPI_ID, &napi_id, &len))
+ error(1, errno, "getsockopt(SO_INCOMING_NAPI_ID)");
+
+ for (i = 0; i < cfg_num_threads; i++) {
+ if (ctxs[i].napi_id == napi_id)
+ return i;
+ }
+
+ error(1, 0, "unknown NAPI ID: %d", napi_id);
+ return -1;
+}
+
+static void run_server(void)
+{
+ pthread_barrier_t setup_done, dispatch_done;
+ int total_conns, accepted = 0, connfd;
+ struct thread_ctx *ctxs;
+ int fd, ret, enable, i;
+ unsigned int ifindex;
+ pthread_t *threads;
+
+ ctxs = calloc(cfg_num_threads, sizeof(*ctxs));
+ threads = calloc(cfg_num_threads, sizeof(*threads));
+ if (!ctxs || !threads)
+ error(1, 0, "calloc()");
fd = socket(AF_INET6, SOCK_STREAM, 0);
if (fd == -1)
@@ -351,32 +457,71 @@ static void run_server(void)
if (ret < 0)
error(1, 0, "bind()");
- flags |= IORING_SETUP_COOP_TASKRUN;
- flags |= IORING_SETUP_SINGLE_ISSUER;
- flags |= IORING_SETUP_DEFER_TASKRUN;
- flags |= IORING_SETUP_SUBMIT_ALL;
- flags |= IORING_SETUP_CQE32;
+ pthread_barrier_init(&setup_done, NULL, cfg_num_threads + 1);
+ pthread_barrier_init(&dispatch_done, NULL, cfg_num_threads + 1);
- io_uring_queue_init(512, &ring, flags);
+ for (i = 0; i < cfg_num_threads; i++) {
+ ctxs[i].queue_id = cfg_queue_id + i;
+ ctxs[i].setup_done = &setup_done;
+ ctxs[i].dispatch_done = &dispatch_done;
+ }
+
+ for (i = 0; i < cfg_num_threads; i++) {
+ ret = pthread_create(&threads[i], NULL,
+ server_worker, &ctxs[i]);
+ if (ret)
+ error(1, ret, "pthread_create()");
+ }
- setup_zcrx(&ring);
if (cfg_dry_run)
- return;
+ goto join;
+
+ pthread_barrier_wait(&setup_done);
if (listen(fd, 1024) < 0)
error(1, 0, "listen()");
- add_accept(&ring, fd);
+ if (cfg_num_threads > 1) {
+ ifindex = if_nametoindex(cfg_ifname);
+ if (!ifindex)
+ error(1, 0, "bad interface name: %s", cfg_ifname);
+ for (i = 0; i < cfg_num_threads; i++)
+ ctxs[i].napi_id = query_napi_id(ifindex,
+ ctxs[i].queue_id);
+ }
- tstop = gettimeofday_ms() + 5000;
- while (!stop && gettimeofday_ms() < tstop)
- server_loop(&ring);
+ total_conns = cfg_num_threads * cfg_num_threads;
+
+ while (accepted < total_conns) {
+ int idx = 0;
+
+ connfd = accept(fd, NULL, NULL);
+ if (connfd < 0)
+ error(1, errno, "accept()");
+
+ if (cfg_num_threads > 1)
+ idx = find_thread_by_conn(ctxs, connfd);
- if (!stop)
- error(1, 0, "test failed\n");
+ if (ctxs[idx].nr_conns >= MAX_CONNS_PER_THREAD)
+ error(1, 0, "worker %d connection overflow", idx);
+ ctxs[idx].connfds[ctxs[idx].nr_conns++] = connfd;
+ accepted++;
+ }
+
+ pthread_barrier_wait(&dispatch_done);
+
+join:
+ for (i = 0; i < cfg_num_threads; i++)
+ pthread_join(threads[i], NULL);
+
+ pthread_barrier_destroy(&setup_done);
+ pthread_barrier_destroy(&dispatch_done);
+ close(fd);
+ free(threads);
+ free(ctxs);
}
-static void run_client(void)
+static void *client_worker(void *arg)
{
ssize_t to_send = cfg_send_size;
ssize_t sent = 0;
@@ -402,12 +547,36 @@ static void run_client(void)
}
close(fd);
+ return NULL;
+}
+
+static void run_client(void)
+{
+ int total_conns = cfg_num_threads * cfg_num_threads;
+ pthread_t *threads;
+ int i, ret;
+
+ threads = calloc(total_conns, sizeof(*threads));
+ if (!threads)
+ error(1, 0, "calloc()");
+
+ for (i = 0; i < total_conns; i++) {
+ ret = pthread_create(&threads[i], NULL, client_worker, NULL);
+ if (ret)
+ error(1, ret, "pthread_create()");
+ }
+
+ for (i = 0; i < total_conns; i++)
+ pthread_join(threads[i], NULL);
+
+ free(threads);
}
static void usage(const char *filepath)
{
error(1, 0, "Usage: %s (-4|-6) (-s|-c) -h<server_ip> -p<port> "
- "-l<payload_size> -i<ifname> -q<rxq_id>", filepath);
+ "-l<payload_size> -i<ifname> -q<rxq_id> -t<num_threads>",
+ filepath);
}
static void parse_opts(int argc, char **argv)
@@ -425,7 +594,7 @@ static void parse_opts(int argc, char **argv)
usage(argv[0]);
cfg_payload_len = max_payload_len;
- while ((c = getopt(argc, argv, "sch:p:l:i:q:o:z:x:d")) != -1) {
+ while ((c = getopt(argc, argv, "sch:p:l:i:q:o:z:x:dt:")) != -1) {
switch (c) {
case 's':
if (cfg_client)
@@ -466,6 +635,9 @@ static void parse_opts(int argc, char **argv)
case 'd':
cfg_dry_run = true;
break;
+ case 't':
+ cfg_num_threads = strtoul(optarg, NULL, 0);
+ break;
}
}
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
index b7a225fe4beae..c833535d8a03c 100755
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
@@ -30,6 +30,13 @@ def create_rss_ctx(cfg):
return int(values)
+def create_rss_ctx_multi(cfg, start, count):
+ """Create an RSS context spanning count queues from start, return its ID."""
+ output = ethtool(f"-X {cfg.ifname} context new start {start} equal {count}").stdout
+ values = re.search(r'New RSS context is (\d+)', output).group(1)
+ return int(values)
+
+
def set_flow_rule(cfg):
output = ethtool(f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.target}").stdout
values = re.search(r'ID (\d+)', output).group(1)
@@ -127,17 +134,56 @@ def _require_ntuple(cfg):
defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+def rss_multiqueue(cfg):
+ """Steer the test flow to a multi-queue RSS context for multi-thread zcrx."""
+ channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+ channels = channels['combined-count']
+ if channels < 3:
+ raise KsftSkipEx('Test requires NETIF with at least 3 combined channels')
+
+ rings = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+ rx_rings = rings['rx']
+ hds_thresh = rings.get('hds-thresh', 0)
+
+ cfg.ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'enabled',
+ 'hds-thresh': 0,
+ 'rx': 64})
+ defer(cfg.ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'unknown',
+ 'hds-thresh': hds_thresh,
+ 'rx': rx_rings})
+ defer(mp_clear_wait, cfg)
+
+ cfg.num_threads = 2
+ cfg.target = channels - cfg.num_threads
+ ethtool(f"-X {cfg.ifname} equal {cfg.target}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ rss_ctx_id = create_rss_ctx_multi(cfg, cfg.target, cfg.num_threads)
+ defer(ethtool, f"-X {cfg.ifname} delete context {rss_ctx_id}")
+
+ flow_rule_id = set_flow_rule_rss(cfg, rss_ctx_id)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+
@ksft_variants([
KsftNamedVariant("single", single),
KsftNamedVariant("rss", rss),
+ KsftNamedVariant("rss_multiqueue", rss_multiqueue),
])
def test_zcrx(cfg, setup) -> None:
cfg.require_ipver('6')
_require_ntuple(cfg)
+ cfg.num_threads = 1
+
setup(cfg)
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target}"
- tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
+
+ rx_cmd = (f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} "
+ f"-q {cfg.target} -t {cfg.num_threads}")
+ tx_cmd = (f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} "
+ f"-l 12840 -t {cfg.num_threads}")
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(cfg.port, proto="tcp")
cmd(tx_cmd, host=cfg.remote)
|
| 3/2 | 2026/09/09 20:57 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11753 output: 73 thoughts: 457 Thoughts: **Fuzzing Decision: No Fuzzing Needed** Okay, let's take a look at this patch and decide if it's worth the fuzzing effort. My initial scan indicates the files modified are: `tools/testing/selftests/drivers/net/hw/Makefile`, `iou-zcrx.c`, and `iou-zcrx.py`. Based on Section 2 of the criteria, it looks like a potential no-go, but let's be thorough. The crucial part here is the "WHEN TO RETURN WorthFuzzing=false" section. It's pretty clear: we only skip fuzzing if all changes fall into specific categories. The key point here is that we have modifications in `tools/`, specifically within the `tools/testing/selftests/drivers/net/hw/` directory. That's a strong indicator, as `tools/` is designated for user-space stuff. We need to confirm it doesn't affect the compiled kernel image (`vmlinux`) or any kernel modules. I need to confirm the files themselves are non-kernel. A quick sanity check of the files involved confirms that they reside entirely within `tools/testing/selftests/`. No core kernel code is touched, from what I'm seeing. So, summing it up, my reasoning is straightforward. The patch *only* modifies user-space selftests under `tools/`, and it definitely doesn't touch the kernel image or modules. Therefore, `WorthFuzzing` is `false`. There's no need to define `FocusSymbols` or `EnableConfigs` since we aren't fuzzing this. Time to call `set-results` with this information. |
| 4/2 | 2026/09/09 20:57 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests and build files under tools/testing/selftests/drivers/net/hw/ and does not touch any kernel code or affect the compiled kernel image.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests and build files under tools/testing/selftests/drivers/net/hw/ and does not touch any kernel code or affect the compiled kernel image.",
"WorthFuzzing": false
} |
| 5/1 | 2026/09/09 20:57 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|