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 <[email protected]>
---
 app/test/meson.build      |   1 +
 app/test/test_pmd_memif.c | 574 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 575 insertions(+)
 create mode 100644 app/test/test_pmd_memif.c

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..6d5e84360e
--- /dev/null
+++ b/app/test/test_pmd_memif.c
@@ -0,0 +1,574 @@
+/* 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
+
+/*
+ * Same socket name and id on both ports makes them a connected pair.
+ * A small bsize relative to the mbuf data room forces chained-descriptor
+ * Rx and Tx.
+ */
+#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,socket-abstract=yes,socket=memif-autotest"
+#define MEMIF_CLI_ARGS 
"role=client,id=0,bsize=512,socket-abstract=yes,socket=memif-autotest"
+
+/* 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
+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(void)
+{
+       struct rte_eth_link link_srv, link_cli;
+       int i;
+
+       for (i = 0; i < CONNECT_TRIES; i++) {
+               if (rte_eth_link_get_nowait(memif_srv_port, &link_srv) == 0 &&
+                   rte_eth_link_get_nowait(memif_cli_port, &link_cli) == 0 &&
+                   link_srv.link_status == RTE_ETH_LINK_UP &&
+                   link_cli.link_status == RTE_ETH_LINK_UP)
+                       return 0;
+               usleep(CONNECT_DELAY_US);
+       }
+       return -1;
+}
+
+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++) {
+                       /* First payload byte must match the seed we sent. */
+                       uint8_t *p = rte_pktmbuf_mtod_offset(rx[i], uint8_t *,
+                                       sizeof(struct rte_ether_hdr));
+                       if (rte_pktmbuf_pkt_len(rx[i]) != PKT_LEN ||
+                                       *p != (uint8_t)got) {
+                               uint16_t k;
+                               unsigned int bad_len =
+                                       rte_pktmbuf_pkt_len(rx[i]);
+                               uint8_t bad_p = *p;
+                               for (k = i; k < nb_rx; k++)
+                                       rte_pktmbuf_free(rx[k]);
+                               TEST_ASSERT(bad_len == PKT_LEN,
+                                           "received packet has wrong length 
%u",
+                                           bad_len);
+                               TEST_ASSERT(0,
+                                           "payload mismatch: got 0x%02x 
expected 0x%02x",
+                                           bad_p, (uint8_t)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;
+}
+
+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 port may be closed without a preceding stop; the driver must tear the
+ * control channel down rather than leak it. Regression test for the
+ * disconnect-on-close fix. Uses a throwaway device so the suite's own
+ * server/client pair is left intact.
+ */
+static int
+test_memif_close_without_stop(void)
+{
+       int port;
+       int ret;
+       struct rte_eth_conf port_conf;
+
+       ret = rte_vdev_init("net_memif_test_tmp",
+                           
"role=server,id=1,socket-abstract=yes,socket=memif-autotest-tmp");
+       if (ret < 0) {
+               printf("could not create throwaway memif device: %s\n",
+                      rte_strerror(-ret));
+               return TEST_SKIPPED;
+       }
+
+       port = find_port("net_memif_test_tmp");
+       TEST_ASSERT(port >= 0, "throwaway memif port not found");
+
+       /*
+        * Configure (but do not start) so the device is closable: the ethdev
+        * layer rejects close on a started port. This exercises dev_close on a
+        * device that never connected, which must still tear down cleanly.
+        */
+       memset(&port_conf, 0, sizeof(port_conf));
+       ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+       if (ret < 0) {
+               rte_vdev_uninit("net_memif_test_tmp");
+               TEST_ASSERT(0, "configure of throwaway port failed: %s",
+                           rte_strerror(-ret));
+       }
+
+       /* Close without ever starting; must not crash or leak. */
+       ret = rte_eth_dev_close(port);
+       TEST_ASSERT(ret == 0, "close without stop failed: %s",
+                   rte_strerror(-ret));
+
+       rte_vdev_uninit("net_memif_test_tmp");
+       return TEST_SUCCESS;
+}
+
+#define BIG_PKT_LEN 4000       /* > default pkt_buffer_size (2048) => chained 
*/
+#define WRAP_PKTS (RING_SIZE + 8)      /* enough to wrap the Tx ring */
+#define BIG_SEG_LEN 1000       /* each mbuf segment carries this many bytes */
+
+/*
+ * 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);
+       }
+       rte_vdev_uninit(MEMIF_SRV_NAME);
+       rte_vdev_uninit(MEMIF_CLI_NAME);
+       if (mp != NULL) {
+               rte_mempool_free(mp);
+               mp = NULL;
+       }
+       memif_srv_port = -1;
+       memif_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_SKIPPED;
+       }
+
+       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;
+       }
+
+       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_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

Reply via email to