All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/8] net/memif: lifecycle fixes and unit test
@ 2026-10-15 14:39 Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 1/8] net/memif: guard against empty Tx burst Sriram Yagnaraman
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

This series fixes a set of error-path and lifecycle bugs in the memif
PMD and adds a unit test for the driver.

The first seven patches are fixes:

 - guard eth_memif_tx() against an empty burst (nb_pkts == 0);
 - close file descriptor 0 instead of leaking it;
 - do not close the EAL-owned memseg fd in zero-copy mode;
 - free the MAC address allocated during probe on failure;
 - unwind a partially-created device on create failure;
 - disconnect on close so a stopped server's control channel is not
   leaked, and make memif_disconnect() safe on a never-started device;
 - fix eth_memif_tx_zc() returning 65535 on an invalid interrupt fd.

The last patch adds a memif PMD unit test (app/test, memif_pmd_autotest):
a server/client vdev pair over a loopback socket covering dev_info, link
state, stats, single-segment loopback (slow and fast path), multi-segment
loopback across a ring wrap, stop/start reconnect, empty Tx burst, and
close without stop.

Verified: memif_pmd_autotest passes 10/10; each commit builds with
-Dwerror=true; check-git-log and checkpatch are clean.

Thanks to Stephen Hemminger and the ai-code-review bot for the v1 review
that prompted all of the v2 changes below.

v2:
 - P1: correct Fixes tag (Tx fast path, not original driver); move the
   empty-burst check after the ring == NULL block.
 - P2: drop the memseg-fd change from this patch; split it out as a
   separate fix (new patch 3) that stops closing the EAL-owned fd
   rather than widening the check to ">= 0".
 - P3 (new): do not close the EAL-owned memseg fd in either
   memif_connect() or memif_free_regions().
 - P4 (MAC leak): document the secondary -1 -> -ENOMEM change.
 - P5 (unwind): add Fixes: multi-process support.
 - P6 (disconnect): rewrite the commit message to name the real
   trigger (server binds pmd->cc without a dev_started check).
 - New patch 7: fix eth_memif_tx_zc() bogus Tx count on invalid fd.
 - Test: add the file to MAINTAINERS; make close-without-stop actually
   connect a peer so the cc leak is exercised; return TEST_FAILED (not
   SKIPPED) on probe failure; add a default-bsize pair to cover the Tx
   fast path; set ring size via rsize and fix the stale comments; verify
   the full payload instead of the first byte.

Sriram Yagnaraman (8):
  net/memif: guard against empty Tx burst
  net/memif: close file descriptor zero
  net/memif: do not close EAL-owned memseg file descriptor
  net/memif: fix MAC address leak on probe failure
  net/memif: unwind partial device on create failure
  net/memif: disconnect on device close
  net/memif: fix bogus Tx count on invalid interrupt FD
  test/memif: add memif PMD unit test

 MAINTAINERS                       |   1 +
 app/test/meson.build              |   1 +
 app/test/test_pmd_memif.c         | 693 ++++++++++++++++++++++++++++++
 drivers/net/memif/memif_socket.c  |  14 +-
 drivers/net/memif/rte_eth_memif.c |  70 +--
 5 files changed, 750 insertions(+), 29 deletions(-)
 create mode 100644 app/test/test_pmd_memif.c

-- 
2.43.7


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 1/8] net/memif: guard against empty Tx burst
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 2/8] net/memif: close file descriptor zero Sriram Yagnaraman
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

eth_memif_tx() reads bufs[0]->pool to pick the mempool for its
single-pool fast-path test, before any check on nb_pkts. A caller that
passes nb_pkts == 0 therefore dereferences bufs[0], out of bounds for an
empty burst. Return early when there is nothing to transmit, after the
ring == NULL check so a secondary process still requests regions.

Fixes: b5613c8f9d0a ("net/memif: add a Tx fast path")
Cc: stable@dpdk.org

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/rte_eth_memif.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index 2d42338e48..2e41eaa459 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -904,6 +904,8 @@ eth_memif_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 				mq->in_port, rte_strerror(-ret));
 		return 0;
 	}
+	if (unlikely(nb_pkts == 0))
+		return 0;
 
 	ring_size = 1 << mq->log2_ring_size;
 	mask = ring_size - 1;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/8] net/memif: close file descriptor zero
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 1/8] net/memif: guard against empty Tx burst Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 3/8] net/memif: do not close EAL-owned memseg file descriptor Sriram Yagnaraman
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

Several cleanup paths guard close() with "fd > 0", which skips a valid
file descriptor of 0 and leaks it. A descriptor of 0 is legitimate (for
example when stdin has been closed before the region or interrupt fd is
created). Use ">= 0" consistently with the -1 sentinel the code already
uses elsewhere, in the memif_free_regions() shm munmap path, the
memif_region_init_shm() error path, the control-channel teardown in
memif_disconnect(), and the SCM_RIGHTS fd check in memif_msg_send().

Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")
Cc: stable@dpdk.org

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/memif_socket.c  | 8 ++++----
 drivers/net/memif/rte_eth_memif.c | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/net/memif/memif_socket.c b/drivers/net/memif/memif_socket.c
index 8231a936e5..5918406271 100644
--- a/drivers/net/memif/memif_socket.c
+++ b/drivers/net/memif/memif_socket.c
@@ -41,7 +41,7 @@ memif_msg_send(int fd, memif_msg_t *msg, int afd)
 	mh.msg_iov = iov;
 	mh.msg_iovlen = 1;
 
-	if (afd > 0) {
+	if (afd >= 0) {
 		memset(&ctl, 0, sizeof(ctl));
 		mh.msg_control = ctl;
 		mh.msg_controllen = sizeof(ctl);
@@ -624,7 +624,7 @@ memif_disconnect(struct rte_eth_dev *dev)
 		}
 
 		ih = pmd->cc->intr_handle;
-		if (rte_intr_fd_get(ih) > 0) {
+		if (rte_intr_fd_get(ih) >= 0) {
 			ret = rte_intr_callback_unregister(ih,
 							memif_intr_handler,
 							pmd->cc);
@@ -664,7 +664,7 @@ memif_disconnect(struct rte_eth_dev *dev)
 				continue;
 		}
 
-		if (rte_intr_fd_get(mq->intr_handle) > 0) {
+		if (rte_intr_fd_get(mq->intr_handle) >= 0) {
 			close(rte_intr_fd_get(mq->intr_handle));
 			rte_intr_fd_set(mq->intr_handle, -1);
 		}
@@ -682,7 +682,7 @@ memif_disconnect(struct rte_eth_dev *dev)
 				continue;
 		}
 
-		if (rte_intr_fd_get(mq->intr_handle) > 0) {
+		if (rte_intr_fd_get(mq->intr_handle) >= 0) {
 			close(rte_intr_fd_get(mq->intr_handle));
 			rte_intr_fd_set(mq->intr_handle, -1);
 		}
diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index 2e41eaa459..6d8d55e570 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -1295,7 +1295,7 @@ memif_free_regions(struct rte_eth_dev *dev)
 			}
 			if (r->addr != NULL) {
 				munmap(r->addr, r->region_size);
-				if (r->fd > 0) {
+				if (r->fd >= 0) {
 					close(r->fd);
 					r->fd = -1;
 				}
@@ -1416,7 +1416,7 @@ memif_region_init_shm(struct rte_eth_dev *dev, uint8_t has_buffers)
 	return ret;
 
 error:
-	if (r->fd > 0)
+	if (r->fd >= 0)
 		close(r->fd);
 	r->fd = -1;
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/8] net/memif: do not close EAL-owned memseg file descriptor
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 1/8] net/memif: guard against empty Tx burst Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 2/8] net/memif: close file descriptor zero Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 4/8] net/memif: fix MAC address leak on probe failure Sriram Yagnaraman
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

In zero-copy mode the per-memseg regions (i > 0) take their fd from
rte_memseg_get_fd(), which rte_memory.h documents as EAL-internal and
read-only. Both memif_connect() and memif_free_regions() closed that fd,
so the next connect handed the peer a stale (possibly reused)
descriptor and the PMD closed a descriptor it does not own. Stop closing
it in both paths and just clear the region fd; the fd is not ours.

Fixes: 43b815d88188 ("net/memif: support zero-copy slave")
Cc: stable@dpdk.org

Suggested-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/rte_eth_memif.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index 6d8d55e570..fb82117d9a 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -1289,9 +1289,9 @@ memif_free_regions(struct rte_eth_dev *dev)
 		if (r != NULL) {
 			/* This is memzone */
 			if (i > 0 && (pmd->flags & ETH_MEMIF_FLAG_ZERO_COPY)) {
+				/* memseg fd is owned by EAL, do not close */
 				r->addr = NULL;
-				if (r->fd > 0)
-					close(r->fd);
+				r->fd = -1;
 			}
 			if (r->addr != NULL) {
 				munmap(r->addr, r->region_size);
@@ -1611,8 +1611,7 @@ memif_connect(struct rte_eth_dev *dev)
 				}
 			}
 			if (i > 0 && (pmd->flags & ETH_MEMIF_FLAG_ZERO_COPY)) {
-				/* close memseg file */
-				close(mr->fd);
+				/* memseg fd is owned by EAL, do not close */
 				mr->fd = -1;
 			}
 		}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 4/8] net/memif: fix MAC address leak on probe failure
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (2 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 3/8] net/memif: do not close EAL-owned memseg file descriptor Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 5/8] net/memif: unwind partial device on create failure Sriram Yagnaraman
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

rte_pmd_memif_probe() allocates the default MAC address up front and
only hands ownership to the device in memif_create() on success. Every
argument-parsing error jumps to the exit label, which freed the kvargs
but not the MAC address, leaking it. The allocation was also dereferenced
by rte_eth_random_addr() with no check that rte_zmalloc() succeeded, and
was done even in the secondary process which never uses it.

Allocate the MAC address only in the primary process after the secondary
path has returned, check it for NULL, and free it at the exit label on
any failure (on success memif_create() owns it via data->mac_addrs).
While here, return -ENOMEM instead of -1 on the secondary-process
process-private allocation failure, matching the primary path.

Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")
Cc: stable@dpdk.org

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/rte_eth_memif.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index fb82117d9a..6a82c597de 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -2238,12 +2238,9 @@ rte_pmd_memif_probe(struct rte_vdev_device *vdev)
 	gid_t owner_gid = -1;
 	uint32_t flags = 0;
 	const char *secret = NULL;
-	struct rte_ether_addr *ether_addr = rte_zmalloc("",
-		sizeof(struct rte_ether_addr), 0);
+	struct rte_ether_addr *ether_addr = NULL;
 	struct rte_eth_dev *eth_dev;
 
-	rte_eth_random_addr(ether_addr->addr_bytes);
-
 	MIF_LOG(INFO, "Initialize MEMIF: %s.", name);
 
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
@@ -2270,7 +2267,7 @@ rte_pmd_memif_probe(struct rte_vdev_device *vdev)
 		if (eth_dev->process_private == NULL) {
 			MIF_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			return -ENOMEM;
 		}
 
 		rte_eth_dev_probing_finish(eth_dev);
@@ -2278,6 +2275,13 @@ rte_pmd_memif_probe(struct rte_vdev_device *vdev)
 		return 0;
 	}
 
+	ether_addr = rte_zmalloc("", sizeof(struct rte_ether_addr), 0);
+	if (ether_addr == NULL) {
+		MIF_LOG(ERR, "Failed to alloc MAC address");
+		return -ENOMEM;
+	}
+	rte_eth_random_addr(ether_addr->addr_bytes);
+
 	ret = rte_mp_action_register(MEMIF_MP_SEND_REGION, memif_mp_send_region);
 	/*
 	 * Primary process can continue probing, but secondary process won't
@@ -2352,6 +2356,12 @@ rte_pmd_memif_probe(struct rte_vdev_device *vdev)
 			   log2_ring_size, pkt_buffer_size, secret, ether_addr);
 
 exit:
+	/*
+	 * On success memif_create() takes ownership of ether_addr
+	 * (data->mac_addrs); free it on any failure or early exit.
+	 */
+	if (ret != 0)
+		rte_free(ether_addr);
 	rte_kvargs_free(kvlist);
 	return ret;
 }
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 5/8] net/memif: unwind partial device on create failure
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (3 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 4/8] net/memif: fix MAC address leak on probe failure Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 6/8] net/memif: disconnect on device close Sriram Yagnaraman
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

memif_create() returned -1 directly when the process-private allocation
or memif_socket_init() failed, leaving the port allocated by
rte_eth_vdev_allocate() registered and the process-private memory
leaked. Jump to an error label that frees the process-private data and
releases the port instead.

Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")
Fixes: c41a04958b09 ("net/memif: support multi-process")
Cc: stable@dpdk.org

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/rte_eth_memif.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index 6a82c597de..fa9847de06 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -1963,18 +1963,18 @@ memif_create(struct rte_vdev_device *vdev, enum memif_role_t role,
 	const unsigned int numa_node = vdev->device.numa_node;
 	const char *name = rte_vdev_device_name(vdev);
 
+	/* Allocate process-private data before reserving the port. */
+	process_private = rte_zmalloc(name, sizeof(struct pmd_process_private),
+				      RTE_CACHE_LINE_SIZE);
+	if (process_private == NULL) {
+		MIF_LOG(ERR, "Failed to alloc memory for process private");
+		return -ENOMEM;
+	}
+
 	eth_dev = rte_eth_vdev_allocate(vdev, sizeof(*pmd));
 	if (eth_dev == NULL) {
 		MIF_LOG(ERR, "%s: Unable to allocate device struct.", name);
-		return -1;
-	}
-
-	process_private = (struct pmd_process_private *)
-		rte_zmalloc(name, sizeof(struct pmd_process_private),
-			    RTE_CACHE_LINE_SIZE);
-
-	if (process_private == NULL) {
-		MIF_LOG(ERR, "Failed to alloc memory for process private");
+		rte_free(process_private);
 		return -1;
 	}
 	eth_dev->process_private = process_private;
@@ -1994,7 +1994,7 @@ memif_create(struct rte_vdev_device *vdev, enum memif_role_t role,
 
 	ret = memif_socket_init(eth_dev, socket_filename);
 	if (ret < 0)
-		return ret;
+		goto error;
 
 	memset(pmd->secret, 0, sizeof(char) * ETH_MEMIF_SECRET_SIZE);
 	if (secret != NULL)
@@ -2029,6 +2029,13 @@ memif_create(struct rte_vdev_device *vdev, enum memif_role_t role,
 	rte_eth_dev_probing_finish(eth_dev);
 
 	return 0;
+
+error:
+	/* rte_eth_dev_release_port() does not free process_private. */
+	rte_free(eth_dev->process_private);
+	eth_dev->process_private = NULL;
+	rte_eth_dev_release_port(eth_dev);
+	return ret;
 }
 
 static int
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 6/8] net/memif: disconnect on device close
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (4 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 5/8] net/memif: unwind partial device on create failure Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 7/8] net/memif: fix bogus Tx count on invalid interrupt FD Sriram Yagnaraman
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

memif_dev_close() enqueued a disconnect message but never flushed it and
never tore down the control channel, leaking the control-channel
interrupt callback, its file descriptor and the pmd->cc allocation.

The leak is reachable on a server that is stopped or was never started:
memif_msg_receive_init() binds pmd->cc to the device without checking
dev_started, so such a server still accepts a peer and holds a live cc
at close time. (rte_eth_dev_close() rejects a started port, so a started
device is always stopped first.) Call memif_disconnect() from close,
which flushes the queued disconnect and releases the control channel;
it is guarded on pmd->cc and clears it, so it is safe when dev_stop
already ran.

memif_disconnect() also walked the per-queue interrupt handles indexed
by the configured ring count without checking that the queue pointer was
set. Closing a device that was configured but never started (so queues
were never allocated) then dereferenced a NULL queue; skip NULL queue
entries.

Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")
Cc: stable@dpdk.org

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/memif_socket.c  | 6 ++++++
 drivers/net/memif/rte_eth_memif.c | 4 ++++
 2 files changed, 10 insertions(+)

diff --git a/drivers/net/memif/memif_socket.c b/drivers/net/memif/memif_socket.c
index 5918406271..6f10e8d970 100644
--- a/drivers/net/memif/memif_socket.c
+++ b/drivers/net/memif/memif_socket.c
@@ -664,6 +664,9 @@ memif_disconnect(struct rte_eth_dev *dev)
 				continue;
 		}
 
+		if (mq == NULL)
+			continue;
+
 		if (rte_intr_fd_get(mq->intr_handle) >= 0) {
 			close(rte_intr_fd_get(mq->intr_handle));
 			rte_intr_fd_set(mq->intr_handle, -1);
@@ -682,6 +685,9 @@ memif_disconnect(struct rte_eth_dev *dev)
 				continue;
 		}
 
+		if (mq == NULL)
+			continue;
+
 		if (rte_intr_fd_get(mq->intr_handle) >= 0) {
 			close(rte_intr_fd_get(mq->intr_handle));
 			rte_intr_fd_set(mq->intr_handle, -1);
diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index fa9847de06..3174d21dc0 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -1717,6 +1717,10 @@ memif_dev_close(struct rte_eth_dev *dev)
 
 	if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
 		memif_msg_enq_disconnect(pmd->cc, "Device closed", 0);
+		/* Flush the disconnect and tear down the control channel;
+		 * memif_disconnect() is idempotent if dev_stop already ran.
+		 */
+		memif_disconnect(dev);
 
 		for (i = 0; i < dev->data->nb_rx_queues; i++)
 			memif_rx_queue_release(dev, i);
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 7/8] net/memif: fix bogus Tx count on invalid interrupt FD
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (5 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 6/8] net/memif: disconnect on device close Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 14:39 ` [PATCH v2 8/8] test/memif: add memif PMD unit test Sriram Yagnaraman
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

eth_memif_tx_zc() returns uint16_t but did "return -1" when the
interrupt FD was invalid, so the caller saw 65535 packets sent after
ring->head had already been published. The FD can legitimately be -1:
memif_init_queues() only warns and continues when eventfd() fails. Skip
the kick when the FD is invalid, as eth_memif_tx() does, and return the
real count.

Fixes: aedd054c5c2a ("drivers: check interrupt file descriptor validity")
Cc: stable@dpdk.org

Suggested-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 drivers/net/memif/rte_eth_memif.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/net/memif/rte_eth_memif.c b/drivers/net/memif/rte_eth_memif.c
index 3174d21dc0..039acd4b12 100644
--- a/drivers/net/memif/rte_eth_memif.c
+++ b/drivers/net/memif/rte_eth_memif.c
@@ -1254,11 +1254,9 @@ eth_memif_tx_zc(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	rte_atomic_store_explicit(&ring->head, slot, rte_memory_order_release);
 
 	/* Send interrupt, if enabled. */
-	if ((ring->flags & MEMIF_RING_FLAG_MASK_INT) == 0) {
+	if ((ring->flags & MEMIF_RING_FLAG_MASK_INT) == 0 &&
+	    rte_intr_fd_get(mq->intr_handle) >= 0) {
 		uint64_t a = 1;
-		if (rte_intr_fd_get(mq->intr_handle) < 0)
-			return -1;
-
 		ssize_t size = write(rte_intr_fd_get(mq->intr_handle),
 				     &a, sizeof(a));
 		if (unlikely(size < 0)) {
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 8/8] test/memif: add memif PMD unit test
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (6 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 7/8] net/memif: fix bogus Tx count on invalid interrupt FD Sriram Yagnaraman
@ 2026-10-15 14:39 ` Sriram Yagnaraman
  2026-10-15 19:51 ` [PATCH v2 0/8] net/memif: lifecycle fixes and " Stephen Hemminger
  2026-10-15 22:49 ` Stephen Hemminger
  9 siblings, 0 replies; 11+ messages in thread
From: Sriram Yagnaraman @ 2026-10-15 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Sriram Yagnaraman, Jakub Grajciar, stable

Add a memif_pmd_autotest unit test, modelled on the TAP PMD test. memif
differs from TAP in that a port is only usable once a peer connects, so
the test creates a server and a client memif port in the same process,
connected over an abstract-namespace socket (the loopback setup from the
memif documentation), and waits for the asynchronous control-channel
handshake to bring both ports up.

The suite checks dev_info reports the memif driver, that the pair reaches
link up, statistics get and reset, a client-to-server loopback burst with
payload verification, multi-segment loopback across a ring wrap with
payload verification (general coverage for the chained-descriptor Rx and
Tx paths), that the pair reconnects after a port restart, that a
zero-length Tx burst is handled, and that a configured-but-not-started
port can be closed without crashing. It is a fast test, runs without
hugepages and under ASan.

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
---
 MAINTAINERS               |   1 +
 app/test/meson.build      |   1 +
 app/test/test_pmd_memif.c | 693 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 695 insertions(+)
 create mode 100644 app/test/test_pmd_memif.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3560b3fd53..8a21bcb946 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1164,6 +1164,7 @@ F: drivers/net/memif/
 F: doc/guides/nics/memif.rst
 F: doc/guides/nics/features/memif.ini
 F: devtools/test-memif.sh
+F: app/test/test_pmd_memif.c
 
 
 Crypto Drivers
diff --git a/app/test/meson.build b/app/test/meson.build
index 9805cf5bee..5ef1ced7ce 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -148,6 +148,7 @@ source_file_deps = {
     'test_pflock.c': [],
     'test_pie.c': ['sched'],
     'test_pmd_af_packet.c': ['net_af_packet', 'ethdev', 'bus_vdev'],
+    'test_pmd_memif.c': ['ethdev', 'net_memif', 'bus_vdev'],
     'test_pmd_pcap.c': ['net_pcap', 'ethdev', 'bus_vdev']  + packet_burst_generator_deps,
     'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps,
     'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'],
diff --git a/app/test/test_pmd_memif.c b/app/test/test_pmd_memif.c
new file mode 100644
index 0000000000..f3b951baa3
--- /dev/null
+++ b/app/test/test_pmd_memif.c
@@ -0,0 +1,693 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+/*
+ * Basic test of memif PMD functionality.
+ * Based off of the TAP PMD test (test_pmd_tap.c).
+ *
+ * memif connects two endpoints over shared memory. Unlike a NIC or TAP
+ * device, a memif port is only usable once its peer connects, so the test
+ * creates a server and a client memif port in this one process, connected
+ * over an abstract-namespace socket (the loopback setup documented in
+ * doc/guides/nics/memif.rst). The control-channel handshake is asynchronous,
+ * so the test waits for both ports to report link up before exercising the
+ * data path.
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* memif PMD is only available on Linux */
+static int
+test_pmd_memif(void)
+{
+	printf("memif PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 4096
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+/* memif ignores nb_tx_desc; the ring size is set via the rsize arg. */
+#define MEMIF_LOG2_RING 8
+
+/*
+ * Same socket name and id on both ports makes them a connected pair.
+ * The small-bsize pair forces chained descriptors; the default-bsize pair
+ * exercises the single-pool Tx fast path.
+ */
+#define MEMIF_SRV_NAME "net_memif_test_srv"
+#define MEMIF_CLI_NAME "net_memif_test_cli"
+#define MEMIF_SRV_ARGS "role=server,id=0,bsize=512,rsize=8,socket-abstract=yes,socket=memif-autotest"
+#define MEMIF_CLI_ARGS "role=client,id=0,bsize=512,rsize=8,socket-abstract=yes,socket=memif-autotest"
+#define MEMIF_FP_SRV_NAME "net_memif_test_fsrv"
+#define MEMIF_FP_CLI_NAME "net_memif_test_fcli"
+#define MEMIF_FP_SRV_ARGS "role=server,id=2,socket-abstract=yes,socket=memif-autotest-fp"
+#define MEMIF_FP_CLI_ARGS "role=client,id=2,socket-abstract=yes,socket=memif-autotest-fp"
+
+/* Time to wait for the asynchronous control-channel handshake. */
+#define CONNECT_TRIES 50
+#define CONNECT_DELAY_US 100000
+
+static struct rte_mempool *mp;
+static int memif_srv_port = -1;
+static int memif_cli_port = -1;
+static int memif_fp_srv_port = -1;
+static int memif_fp_cli_port = -1;
+
+static int
+find_port(const char *frag)
+{
+	uint16_t port_id;
+	char name[RTE_ETH_NAME_MAX_LEN];
+
+	RTE_ETH_FOREACH_DEV(port_id) {
+		if (rte_eth_dev_get_name_by_port(port_id, name) == 0 &&
+		    strstr(name, frag) != NULL)
+			return port_id;
+	}
+	return -1;
+}
+
+static int
+configure_port(int port)
+{
+	struct rte_eth_conf port_conf;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(port_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Wait until both ports report link up, i.e. the peers have connected. */
+static int
+wait_for_link_pair(int p0, int p1)
+{
+	struct rte_eth_link l0, l1;
+	int i;
+
+	for (i = 0; i < CONNECT_TRIES; i++) {
+		if (rte_eth_link_get_nowait(p0, &l0) == 0 &&
+		    rte_eth_link_get_nowait(p1, &l1) == 0 &&
+		    l0.link_status == RTE_ETH_LINK_UP &&
+		    l1.link_status == RTE_ETH_LINK_UP)
+			return 0;
+		usleep(CONNECT_DELAY_US);
+	}
+	return -1;
+}
+
+static int
+wait_for_link(void)
+{
+	return wait_for_link_pair(memif_srv_port, memif_cli_port);
+}
+
+static struct rte_mbuf *
+create_test_packet(uint16_t pkt_len, uint8_t seed)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(mp);
+	if (mbuf == NULL)
+		return NULL;
+
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(mbuf, pkt_len);
+	if (eth_hdr == NULL) {
+		rte_pktmbuf_free(mbuf);
+		return NULL;
+	}
+
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	payload = (uint8_t *)(eth_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr); i++)
+		payload[i] = (uint8_t)(seed + i);
+
+	return mbuf;
+}
+
+static int
+test_memif_dev_info(void)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	ret = rte_eth_dev_info_get(memif_srv_port, &dev_info);
+	TEST_ASSERT(ret == 0, "dev_info_get failed: %s", rte_strerror(-ret));
+	TEST_ASSERT(strcmp(dev_info.driver_name, "net_memif") == 0,
+		    "unexpected driver name: %s", dev_info.driver_name);
+	return TEST_SUCCESS;
+}
+
+static int
+test_memif_link_up(void)
+{
+	/* The pair must have connected during setup. */
+	TEST_ASSERT(wait_for_link() == 0,
+		    "memif server/client did not reach link up");
+	return TEST_SUCCESS;
+}
+
+static int
+test_memif_stats_get(void)
+{
+	struct rte_eth_stats stats;
+
+	TEST_ASSERT(rte_eth_stats_get(memif_srv_port, &stats) == 0,
+		    "stats_get failed for server port");
+	TEST_ASSERT(rte_eth_stats_get(memif_cli_port, &stats) == 0,
+		    "stats_get failed for client port");
+	return TEST_SUCCESS;
+}
+
+static int
+test_memif_stats_reset(void)
+{
+	struct rte_eth_stats stats;
+
+	TEST_ASSERT(rte_eth_stats_reset(memif_srv_port) == 0,
+		    "stats_reset failed");
+	TEST_ASSERT(rte_eth_stats_get(memif_srv_port, &stats) == 0,
+		    "stats_get after reset failed");
+	TEST_ASSERT(stats.ipackets == 0 && stats.opackets == 0 &&
+		    stats.ibytes == 0 && stats.obytes == 0,
+		    "stats not zero after reset");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Send a burst from the client and receive it on the server, verifying the
+ * payload is delivered intact. This exercises the Rx/Tx data path over the
+ * connected memif pair.
+ */
+static int
+test_memif_send_receive(void)
+{
+	struct rte_mbuf *tx[MAX_PKT_BURST];
+	struct rte_mbuf *rx[MAX_PKT_BURST];
+	const uint16_t nb = MAX_PKT_BURST / 2;
+	uint16_t nb_tx, nb_rx, got;
+	int i, tries;
+
+	TEST_ASSERT(wait_for_link() == 0, "link not up before send/receive");
+
+	for (i = 0; i < nb; i++) {
+		tx[i] = create_test_packet(PKT_LEN, (uint8_t)i);
+		if (tx[i] == NULL) {
+			while (--i >= 0)
+				rte_pktmbuf_free(tx[i]);
+			TEST_ASSERT(0, "failed to build test packet");
+		}
+	}
+
+	nb_tx = rte_eth_tx_burst(memif_cli_port, 0, tx, nb);
+	for (i = nb_tx; i < nb; i++)
+		rte_pktmbuf_free(tx[i]);
+	TEST_ASSERT(nb_tx > 0, "client transmitted no packets");
+
+	/* Receive may take a moment; poll a few times. */
+	got = 0;
+	for (tries = 0; tries < 10 && got < nb_tx; tries++) {
+		nb_rx = rte_eth_rx_burst(memif_srv_port, 0, rx, MAX_PKT_BURST);
+		for (i = 0; i < nb_rx; i++) {
+			/* Payload must match the position-dependent pattern. */
+			uint8_t *p = rte_pktmbuf_mtod_offset(rx[i], uint8_t *,
+					sizeof(struct rte_ether_hdr));
+			uint16_t plen = PKT_LEN -
+					sizeof(struct rte_ether_hdr);
+			uint16_t k;
+			int bad = rte_pktmbuf_pkt_len(rx[i]) != PKT_LEN;
+
+			for (k = 0; !bad && k < plen; k++)
+				if (p[k] != (uint8_t)(got + k))
+					bad = 1;
+			if (bad) {
+				for (k = i; k < nb_rx; k++)
+					rte_pktmbuf_free(rx[k]);
+				TEST_ASSERT(0,
+					    "packet %u payload/length mismatch",
+					    got);
+			}
+			rte_pktmbuf_free(rx[i]);
+			got++;
+		}
+		if (got < nb_tx)
+			usleep(10000);
+	}
+
+	TEST_ASSERT(got == nb_tx,
+		    "received %u packets, expected %u", got, nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Single-segment loopback on the default-bsize pair, where bsize exceeds
+ * the mbuf data room so eth_memif_tx() takes the single-pool fast path.
+ */
+static int
+test_memif_fastpath(void)
+{
+	struct rte_mbuf *tx[MAX_PKT_BURST];
+	struct rte_mbuf *rx[MAX_PKT_BURST];
+	const uint16_t nb = MAX_PKT_BURST / 2;
+	uint16_t nb_tx, got;
+	int i, tries;
+
+	TEST_ASSERT(wait_for_link_pair(memif_fp_srv_port, memif_fp_cli_port)
+		    == 0, "fast-path pair not up");
+
+	for (i = 0; i < nb; i++) {
+		tx[i] = create_test_packet(PKT_LEN, (uint8_t)i);
+		if (tx[i] == NULL) {
+			while (--i >= 0)
+				rte_pktmbuf_free(tx[i]);
+			TEST_ASSERT(0, "failed to build test packet");
+		}
+	}
+
+	nb_tx = rte_eth_tx_burst(memif_fp_cli_port, 0, tx, nb);
+	for (i = nb_tx; i < nb; i++)
+		rte_pktmbuf_free(tx[i]);
+	TEST_ASSERT(nb_tx > 0, "fast-path client transmitted no packets");
+
+	got = 0;
+	for (tries = 0; tries < 10 && got < nb_tx; tries++) {
+		uint16_t nb_rx = rte_eth_rx_burst(memif_fp_srv_port, 0, rx,
+						  MAX_PKT_BURST);
+		for (i = 0; i < nb_rx; i++) {
+			TEST_ASSERT(rte_pktmbuf_pkt_len(rx[i]) == PKT_LEN,
+				    "fast-path packet wrong length");
+			rte_pktmbuf_free(rx[i]);
+			got++;
+		}
+		if (got < nb_tx)
+			usleep(10000);
+	}
+
+	TEST_ASSERT(got == nb_tx, "fast-path received %u expected %u",
+		    got, nb_tx);
+	return TEST_SUCCESS;
+}
+
+static int
+test_memif_stop_start(void)
+{
+	TEST_ASSERT(rte_eth_dev_stop(memif_cli_port) == 0,
+		    "stop client failed");
+	TEST_ASSERT(rte_eth_dev_start(memif_cli_port) == 0,
+		    "restart client failed");
+	/* After a restart the pair must reconnect. */
+	TEST_ASSERT(wait_for_link() == 0, "link did not recover after restart");
+	return TEST_SUCCESS;
+}
+
+/*
+ * A zero-length Tx burst must be accepted and return 0 without touching
+ * the (empty) mbuf array. Regression test for the empty-burst guard in
+ * eth_memif_tx().
+ */
+static int
+test_memif_tx_empty_burst(void)
+{
+	struct rte_mbuf *dummy = NULL;
+
+	TEST_ASSERT(wait_for_link() == 0, "link not up before empty burst");
+	TEST_ASSERT(rte_eth_tx_burst(memif_cli_port, 0, &dummy, 0) == 0,
+		    "zero-length Tx burst did not return 0");
+	return TEST_SUCCESS;
+}
+
+/*
+ * A server port that accepted a peer while stopped (or never started) must
+ * tear down its control channel on close. memif_msg_receive_init() binds
+ * pmd->cc without checking dev_started, so a client is connected to the
+ * unstarted throwaway server to make its cc live. Regression test for the
+ * disconnect-on-close fix; uses throwaway devices so the suite pair stays
+ * intact.
+ */
+static int
+test_memif_close_without_stop(void)
+{
+	int sport, cport, ret;
+	struct rte_eth_conf port_conf;
+
+	ret = rte_vdev_init("net_memif_test_tmps",
+			    "role=server,id=1,socket-abstract=yes,socket=memif-autotest-tmp");
+	if (ret < 0) {
+		printf("could not create throwaway server: %s\n",
+		       rte_strerror(-ret));
+		return TEST_FAILED;
+	}
+
+	sport = find_port("net_memif_test_tmps");
+	if (sport < 0) {
+		rte_vdev_uninit("net_memif_test_tmps");
+		TEST_ASSERT(0, "throwaway server port not found");
+	}
+
+	/* Configure but do not start: close is rejected on a started port. */
+	memset(&port_conf, 0, sizeof(port_conf));
+	ret = rte_eth_dev_configure(sport, 1, 1, &port_conf);
+	if (ret < 0) {
+		rte_vdev_uninit("net_memif_test_tmps");
+		TEST_ASSERT(0, "configure of throwaway server failed: %s",
+			    rte_strerror(-ret));
+	}
+
+	/* Client connects, so the stopped server binds pmd->cc. */
+	ret = rte_vdev_init("net_memif_test_tmpc",
+			    "role=client,id=1,socket-abstract=yes,socket=memif-autotest-tmp");
+	if (ret < 0) {
+		rte_vdev_uninit("net_memif_test_tmps");
+		TEST_ASSERT(0, "could not create throwaway client: %s",
+			    rte_strerror(-ret));
+	}
+	cport = find_port("net_memif_test_tmpc");
+	if (cport < 0 || configure_port(cport) < 0) {
+		rte_vdev_uninit("net_memif_test_tmpc");
+		rte_vdev_uninit("net_memif_test_tmps");
+		TEST_ASSERT(0, "throwaway client setup failed");
+	}
+	usleep(CONNECT_DELAY_US);	/* let the server accept and bind cc */
+
+	/* Close the stopped server with a live cc; must not crash or leak. */
+	ret = rte_eth_dev_close(sport);
+	rte_eth_dev_stop(cport);
+	rte_eth_dev_close(cport);
+	rte_vdev_uninit("net_memif_test_tmpc");
+	rte_vdev_uninit("net_memif_test_tmps");
+	TEST_ASSERT(ret == 0, "close without stop failed: %s",
+		    rte_strerror(-ret));
+
+	return TEST_SUCCESS;
+}
+
+#define BIG_PKT_LEN 4000	/* > bsize (512) => chained descriptors */
+#define BIG_SEG_LEN 1000	/* each mbuf segment carries this many bytes */
+/* ~8 descriptors per packet; enough to wrap the 2^MEMIF_LOG2_RING ring */
+#define WRAP_PKTS ((1 << MEMIF_LOG2_RING) / 8 + 8)
+
+/*
+ * Build a multi-segment mbuf of pkt_len bytes, chaining as many pool mbufs
+ * as needed (BIG_SEG_LEN bytes each) with a position-dependent pattern.
+ */
+static struct rte_mbuf *
+create_big_packet(uint16_t pkt_len, uint8_t seed)
+{
+	struct rte_mbuf *head = NULL, *tail = NULL;
+	uint16_t done = 0;
+
+	while (done < pkt_len) {
+		struct rte_mbuf *seg = rte_pktmbuf_alloc(mp);
+		uint16_t seg_len = RTE_MIN((uint16_t)BIG_SEG_LEN,
+					   (uint16_t)(pkt_len - done));
+		uint8_t *data;
+		uint16_t i;
+
+		if (seg == NULL) {
+			rte_pktmbuf_free(head);
+			return NULL;
+		}
+		data = (uint8_t *)rte_pktmbuf_append(seg, seg_len);
+		for (i = 0; i < seg_len; i++)
+			data[i] = (uint8_t)(seed + done + i);
+
+		if (head == NULL) {
+			head = seg;
+			tail = seg;
+		} else {
+			tail->next = seg;
+			tail = seg;
+			head->nb_segs++;
+			head->pkt_len += seg_len;
+		}
+		done += seg_len;
+	}
+	return head;
+}
+
+/* Verify a received packet's payload, walking its segment chain. */
+static int
+check_big_packet(struct rte_mbuf *m, uint16_t pkt_len, uint8_t seed)
+{
+	uint16_t off = 0;
+	struct rte_mbuf *seg;
+
+	if (rte_pktmbuf_pkt_len(m) != pkt_len)
+		return -1;
+	for (seg = m; seg != NULL; seg = seg->next) {
+		const uint8_t *data = rte_pktmbuf_mtod(seg, const uint8_t *);
+		uint16_t i;
+
+		for (i = 0; i < rte_pktmbuf_data_len(seg); i++, off++)
+			if (data[i] != (uint8_t)(seed + off))
+				return -1;
+	}
+	return off == pkt_len ? 0 : -1;
+}
+
+/*
+ * Send multi-segment packets larger than the memif buffer size, repeated
+ * enough to wrap the ring, and verify each is delivered intact. Exercises
+ * the chained-descriptor Rx and Tx paths across a ring wrap.
+ */
+static int
+test_memif_multiseg(void)
+{
+	unsigned int sent = 0, recv = 0;
+	unsigned int iter;
+
+	TEST_ASSERT(wait_for_link() == 0, "link not up before multiseg test");
+
+	for (iter = 0; iter < WRAP_PKTS; iter++) {
+		struct rte_mbuf *tx, *rx[MAX_PKT_BURST];
+		uint16_t n, j;
+		int tries;
+
+		tx = create_big_packet(BIG_PKT_LEN, (uint8_t)iter);
+		TEST_ASSERT(tx != NULL, "failed to build big packet %u", iter);
+		if (tx->nb_segs <= 1) {
+			rte_pktmbuf_free(tx);
+			TEST_ASSERT(0, "test packet is not multi-segment");
+		}
+
+		if (rte_eth_tx_burst(memif_cli_port, 0, &tx, 1) != 1) {
+			rte_pktmbuf_free(tx);
+			continue;
+		}
+		sent++;
+
+		for (tries = 0; tries < 10; tries++) {
+			n = rte_eth_rx_burst(memif_srv_port, 0, rx,
+					     MAX_PKT_BURST);
+			for (j = 0; j < n; j++) {
+				if (check_big_packet(rx[j], BIG_PKT_LEN,
+						(uint8_t)recv) != 0) {
+					uint16_t k;
+					for (k = j; k < n; k++)
+						rte_pktmbuf_free(rx[k]);
+					TEST_ASSERT(0,
+						"multi-seg payload corrupted at pkt %u",
+						recv);
+				}
+				rte_pktmbuf_free(rx[j]);
+				recv++;
+			}
+			if (recv >= sent)
+				break;
+			usleep(2000);
+		}
+	}
+
+	TEST_ASSERT(recv == sent,
+		    "multi-seg: sent %u received %u", sent, recv);
+	return TEST_SUCCESS;
+}
+
+static void
+test_memif_cleanup(void)
+{
+	if (memif_srv_port >= 0) {
+		rte_eth_dev_stop(memif_srv_port);
+		rte_eth_dev_close(memif_srv_port);
+	}
+	if (memif_cli_port >= 0) {
+		rte_eth_dev_stop(memif_cli_port);
+		rte_eth_dev_close(memif_cli_port);
+	}
+	if (memif_fp_srv_port >= 0) {
+		rte_eth_dev_stop(memif_fp_srv_port);
+		rte_eth_dev_close(memif_fp_srv_port);
+	}
+	if (memif_fp_cli_port >= 0) {
+		rte_eth_dev_stop(memif_fp_cli_port);
+		rte_eth_dev_close(memif_fp_cli_port);
+	}
+	rte_vdev_uninit(MEMIF_SRV_NAME);
+	rte_vdev_uninit(MEMIF_CLI_NAME);
+	rte_vdev_uninit(MEMIF_FP_SRV_NAME);
+	rte_vdev_uninit(MEMIF_FP_CLI_NAME);
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+	memif_srv_port = -1;
+	memif_cli_port = -1;
+	memif_fp_srv_port = -1;
+	memif_fp_cli_port = -1;
+}
+
+static int
+test_memif_setup(void)
+{
+	int ret;
+
+	mp = rte_pktmbuf_pool_create("memif_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	ret = rte_vdev_init(MEMIF_SRV_NAME, MEMIF_SRV_ARGS);
+	if (ret < 0) {
+		printf("Error: failed to create memif server: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return TEST_FAILED;
+	}
+
+	ret = rte_vdev_init(MEMIF_CLI_NAME, MEMIF_CLI_ARGS);
+	if (ret < 0) {
+		printf("Error: failed to create memif client: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit(MEMIF_SRV_NAME);
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	memif_srv_port = find_port(MEMIF_SRV_NAME);
+	memif_cli_port = find_port(MEMIF_CLI_NAME);
+	if (memif_srv_port < 0 || memif_cli_port < 0) {
+		printf("Error: failed to find memif ports\n");
+		test_memif_cleanup();
+		return -1;
+	}
+
+	if (configure_port(memif_srv_port) < 0 ||
+	    configure_port(memif_cli_port) < 0) {
+		test_memif_cleanup();
+		return -1;
+	}
+
+	/* Second pair with default bsize to exercise the Tx fast path. */
+	if (rte_vdev_init(MEMIF_FP_SRV_NAME, MEMIF_FP_SRV_ARGS) < 0 ||
+	    rte_vdev_init(MEMIF_FP_CLI_NAME, MEMIF_FP_CLI_ARGS) < 0) {
+		printf("Error: failed to create fast-path memif pair\n");
+		test_memif_cleanup();
+		return TEST_FAILED;
+	}
+
+	memif_fp_srv_port = find_port(MEMIF_FP_SRV_NAME);
+	memif_fp_cli_port = find_port(MEMIF_FP_CLI_NAME);
+	if (memif_fp_srv_port < 0 || memif_fp_cli_port < 0) {
+		printf("Error: failed to find fast-path memif ports\n");
+		test_memif_cleanup();
+		return TEST_FAILED;
+	}
+
+	if (configure_port(memif_fp_srv_port) < 0 ||
+	    configure_port(memif_fp_cli_port) < 0) {
+		test_memif_cleanup();
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct unit_test_suite test_pmd_memif_suite = {
+	.setup = test_memif_setup,
+	.teardown = test_memif_cleanup,
+	.suite_name = "memif PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_memif_dev_info),
+		TEST_CASE(test_memif_link_up),
+		TEST_CASE(test_memif_stats_get),
+		TEST_CASE(test_memif_send_receive),
+		TEST_CASE(test_memif_fastpath),
+		TEST_CASE(test_memif_multiseg),
+		TEST_CASE(test_memif_stats_reset),
+		TEST_CASE(test_memif_stop_start),
+		TEST_CASE(test_memif_tx_empty_burst),
+		TEST_CASE(test_memif_close_without_stop),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_memif(void)
+{
+	return unit_test_suite_runner(&test_pmd_memif_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(memif_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_memif);
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 0/8] net/memif: lifecycle fixes and unit test
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (7 preceding siblings ...)
  2026-10-15 14:39 ` [PATCH v2 8/8] test/memif: add memif PMD unit test Sriram Yagnaraman
@ 2026-10-15 19:51 ` Stephen Hemminger
  2026-10-15 22:49 ` Stephen Hemminger
  9 siblings, 0 replies; 11+ messages in thread
From: Stephen Hemminger @ 2026-10-15 19:51 UTC (permalink / raw)
  To: Sriram Yagnaraman; +Cc: dev, Jakub Grajciar, stable

On Thu, 15 Oct 2026 16:39:37 +0200
Sriram Yagnaraman <sriram.yagnaraman@ericsson.com> wrote:

> This series fixes a set of error-path and lifecycle bugs in the memif
> PMD and adds a unit test for the driver.
> 
> The first seven patches are fixes:
> 
>  - guard eth_memif_tx() against an empty burst (nb_pkts == 0);
>  - close file descriptor 0 instead of leaking it;
>  - do not close the EAL-owned memseg fd in zero-copy mode;
>  - free the MAC address allocated during probe on failure;
>  - unwind a partially-created device on create failure;
>  - disconnect on close so a stopped server's control channel is not
>    leaked, and make memif_disconnect() safe on a never-started device;
>  - fix eth_memif_tx_zc() returning 65535 on an invalid interrupt fd.
> 
> The last patch adds a memif PMD unit test (app/test, memif_pmd_autotest):
> a server/client vdev pair over a loopback socket covering dev_info, link
> state, stats, single-segment loopback (slow and fast path), multi-segment
> loopback across a ring wrap, stop/start reconnect, empty Tx burst, and
> close without stop.
> 
> Verified: memif_pmd_autotest passes 10/10; each commit builds with
> -Dwerror=true; check-git-log and checkpatch are clean.
> 
> Thanks to Stephen Hemminger and the ai-code-review bot for the v1 review
> that prompted all of the v2 changes below.

More semi-automated AI review feedback

2/8 and 6/8 conflict with d9191f684c ("interrupts: add helpers to
close FD") on main, which already converted the queue fd closes in
memif_disconnect() to rte_intr_fd_close(). Rebase, keeping
rte_intr_fd_close() and the new mq == NULL checks. Resolved that way,
every commit builds with -Dwerror=true. Not run.

The v1 comments are addressed. One of them was wrong, and that error
went into 6/8 and 8/8; my mistake. Details below.


[PATCH v2 6/8] net/memif: disconnect on device close

Warning: the reachable case is a server that was started and then
stopped, not one that was never started. memif_msg_receive_init()
skips any server with ETH_MEMIF_FLAG_DISABLED set. memif_create() sets
that flag and only memif_connect_server() clears it, at dev_start.
dev_stop never sets it again, so:

  never started:       peer rejected, pmd->cc is NULL at close
  started, stopped:    peer accepted, pmd->cc bound, and on CONNECT
                       memif_connect() sets CONNECTED and link up on
                       a stopped port

The second case is the bug. Fix it at the source in a separate patch
by refusing peers while stopped:

	static int
	memif_dev_stop(struct rte_eth_dev *dev)
	{
		...
		pmd->flags |= ETH_MEMIF_FLAG_DISABLED;
		memif_disconnect(dev);

  Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")

Keep the memif_disconnect() call in close as a backstop for an init
racing with stop, and change the commit message to "started and then
stopped".


[PATCH v2 8/8] test/memif: add memif PMD unit test

Warning: test_memif_close_without_stop() still never reaches the cc
teardown. The throwaway server is never started, so the client's init
is rejected and pmd->cc stays NULL. I suggested that setup in v1, and
it was wrong. What the test does cover is close on a configured but
never-started port, i.e. the mq == NULL skip. Reword its comment to
say so.

Once the DISABLED-on-stop fix is in, test that instead: start the
server, stop it, start a client, and check the server stays down:

	TEST_ASSERT(configure_port(sport) == 0, "server start failed");
	TEST_ASSERT(rte_eth_dev_stop(sport) == 0, "server stop failed");
	/* create and start the client as now */
	TEST_ASSERT(wait_for_link_pair(sport, cport) != 0,
		    "stopped server accepted a peer");

Without that fix, this check fails today: the stopped server reports
link up.

Info: test_memif_fastpath() checks only the length. Verify the payload
as test_memif_send_receive() now does.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 0/8] net/memif: lifecycle fixes and unit test
  2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
                   ` (8 preceding siblings ...)
  2026-10-15 19:51 ` [PATCH v2 0/8] net/memif: lifecycle fixes and " Stephen Hemminger
@ 2026-10-15 22:49 ` Stephen Hemminger
  9 siblings, 0 replies; 11+ messages in thread
From: Stephen Hemminger @ 2026-10-15 22:49 UTC (permalink / raw)
  To: Sriram Yagnaraman; +Cc: dev, Jakub Grajciar, stable

On Thu, 15 Oct 2026 16:39:37 +0200
Sriram Yagnaraman <sriram.yagnaraman@ericsson.com> wrote:

> This series fixes a set of error-path and lifecycle bugs in the memif
> PMD and adds a unit test for the driver.
> 
> The first seven patches are fixes:
> 
>  - guard eth_memif_tx() against an empty burst (nb_pkts == 0);
>  - close file descriptor 0 instead of leaking it;
>  - do not close the EAL-owned memseg fd in zero-copy mode;
>  - free the MAC address allocated during probe on failure;
>  - unwind a partially-created device on create failure;
>  - disconnect on close so a stopped server's control channel is not
>    leaked, and make memif_disconnect() safe on a never-started device;
>  - fix eth_memif_tx_zc() returning 65535 on an invalid interrupt fd.
> 
> The last patch adds a memif PMD unit test (app/test, memif_pmd_autotest):
> a server/client vdev pair over a loopback socket covering dev_info, link
> state, stats, single-segment loopback (slow and fast path), multi-segment
> loopback across a ring wrap, stop/start reconnect, empty Tx burst, and
> close without stop.
> 
> Verified: memif_pmd_autotest passes 10/10; each commit builds with
> -Dwerror=true; check-git-log and checkpatch are clean.
> 
> Thanks to Stephen Hemminger and the ai-code-review bot for the v1 review
> that prompted all of the v2 changes below.


More from semi-manual AI code review.

2/8 and 6/8 conflict with d9191f684c ("interrupts: add helpers to
close FD") on main, which already converted the queue fd closes in
memif_disconnect() to rte_intr_fd_close(). Rebase, keeping
rte_intr_fd_close() and the new mq == NULL checks. Resolved that way,
every commit builds with -Dwerror=true. Not run.

The v1 comments are addressed. One of them was wrong, and that error
went into 6/8 and 8/8; my mistake. Details below.


[PATCH v2 6/8] net/memif: disconnect on device close

Warning: the reachable case is a server that was started and then
stopped, not one that was never started. memif_msg_receive_init()
skips any server with ETH_MEMIF_FLAG_DISABLED set. memif_create() sets
that flag and only memif_connect_server() clears it, at dev_start.
dev_stop never sets it again, so:

  never started:       peer rejected, pmd->cc is NULL at close
  started, stopped:    peer accepted, pmd->cc bound, and on CONNECT
                       memif_connect() sets CONNECTED and link up on
                       a stopped port

The second case is the bug. Fix it at the source in a separate patch
by refusing peers while stopped:

	static int
	memif_dev_stop(struct rte_eth_dev *dev)
	{
		...
		pmd->flags |= ETH_MEMIF_FLAG_DISABLED;
		memif_disconnect(dev);

  Fixes: 09c7e63a71f9 ("net/memif: introduce memory interface PMD")

Keep the memif_disconnect() call in close as a backstop for an init
racing with stop, and change the commit message to "started and then
stopped".


[PATCH v2 8/8] test/memif: add memif PMD unit test

Warning: test_memif_close_without_stop() still never reaches the cc
teardown. The throwaway server is never started, so the client's init
is rejected and pmd->cc stays NULL. I suggested that setup in v1, and
it was wrong. What the test does cover is close on a configured but
never-started port, i.e. the mq == NULL skip. Reword its comment to
say so.

Once the DISABLED-on-stop fix is in, test that instead: start the
server, stop it, start a client, and check the server stays down:

	TEST_ASSERT(configure_port(sport) == 0, "server start failed");
	TEST_ASSERT(rte_eth_dev_stop(sport) == 0, "server stop failed");
	/* create and start the client as now */
	TEST_ASSERT(wait_for_link_pair(sport, cport) != 0,
		    "stopped server accepted a peer");

Without that fix, this check fails today: the stopped server reports
link up.

Info: test_memif_fastpath() checks only the length. Verify the payload
as test_memif_send_receive() now does.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-10-15 22:49 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 14:39 [PATCH v2 0/8] net/memif: lifecycle fixes and unit test Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 1/8] net/memif: guard against empty Tx burst Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 2/8] net/memif: close file descriptor zero Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 3/8] net/memif: do not close EAL-owned memseg file descriptor Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 4/8] net/memif: fix MAC address leak on probe failure Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 5/8] net/memif: unwind partial device on create failure Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 6/8] net/memif: disconnect on device close Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 7/8] net/memif: fix bogus Tx count on invalid interrupt FD Sriram Yagnaraman
2026-10-15 14:39 ` [PATCH v2 8/8] test/memif: add memif PMD unit test Sriram Yagnaraman
2026-10-15 19:51 ` [PATCH v2 0/8] net/memif: lifecycle fixes and " Stephen Hemminger
2026-10-15 22:49 ` Stephen Hemminger

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.