aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/netmap/ctrl-api-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/netmap/ctrl-api-test.c')
-rw-r--r--tests/sys/netmap/ctrl-api-test.c2328
1 files changed, 2328 insertions, 0 deletions
diff --git a/tests/sys/netmap/ctrl-api-test.c b/tests/sys/netmap/ctrl-api-test.c
new file mode 100644
index 000000000000..36c131980360
--- /dev/null
+++ b/tests/sys/netmap/ctrl-api-test.c
@@ -0,0 +1,2328 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2018 Vincenzo Maffione
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This program contains a suite of unit tests for the netmap control device.
+ *
+ * On FreeBSD, you can run these tests with Kyua once installed in the system:
+ * # kyua test -k /usr/tests/sys/netmap/Kyuafile
+ *
+ * On Linux, you can run them directly:
+ * # ./ctrl-api-test
+ */
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libnetmap.h>
+#include <net/if.h>
+#include <net/netmap.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stddef.h>
+
+#ifdef __FreeBSD__
+static int
+eventfd(int x __unused, int y __unused)
+{
+ errno = ENODEV;
+ return -1;
+}
+#else /* __linux__ */
+#include <sys/eventfd.h>
+#endif
+
+#define NM_IFNAMSZ 64
+
+static int
+exec_command(int argc, const char *const argv[])
+{
+ pid_t child_pid;
+ pid_t wret;
+ int child_status;
+ int i;
+
+ printf("Executing command: ");
+ for (i = 0; i < argc - 1; i++) {
+ if (!argv[i]) {
+ /* Invalid argument. */
+ return -1;
+ }
+ if (i > 0) {
+ putchar(' ');
+ }
+ printf("%s", argv[i]);
+ }
+ putchar('\n');
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ char **av;
+ int fds[3];
+
+ /* Child process. Redirect stdin, stdout
+ * and stderr. */
+ for (i = 0; i < 3; i++) {
+ close(i);
+ fds[i] = open("/dev/null", O_RDONLY);
+ if (fds[i] < 0) {
+ for (i--; i >= 0; i--) {
+ close(fds[i]);
+ }
+ return -1;
+ }
+ }
+
+ /* Make a copy of the arguments, passing them to execvp. */
+ av = calloc(argc, sizeof(av[0]));
+ if (!av) {
+ exit(EXIT_FAILURE);
+ }
+ for (i = 0; i < argc - 1; i++) {
+ av[i] = strdup(argv[i]);
+ if (!av[i]) {
+ exit(EXIT_FAILURE);
+ }
+ }
+ execvp(av[0], av);
+ perror("execvp()");
+ exit(EXIT_FAILURE);
+ }
+
+ wret = waitpid(child_pid, &child_status, 0);
+ if (wret < 0) {
+ fprintf(stderr, "waitpid() failed: %s\n", strerror(errno));
+ return wret;
+ }
+ if (WIFEXITED(child_status)) {
+ return WEXITSTATUS(child_status);
+ }
+
+ return -1;
+}
+
+
+#define THRET_SUCCESS ((void *)128)
+#define THRET_FAILURE ((void *)0)
+
+struct TestContext {
+ char ifname[NM_IFNAMSZ];
+ char ifname_ext[NM_IFNAMSZ];
+ char bdgname[NM_IFNAMSZ];
+ uint32_t nr_tx_slots; /* slots in tx rings */
+ uint32_t nr_rx_slots; /* slots in rx rings */
+ uint16_t nr_tx_rings; /* number of tx rings */
+ uint16_t nr_rx_rings; /* number of rx rings */
+ uint16_t nr_host_tx_rings; /* number of host tx rings */
+ uint16_t nr_host_rx_rings; /* number of host rx rings */
+ uint16_t nr_mem_id; /* id of the memory allocator */
+ uint16_t nr_ringid; /* ring(s) we care about */
+ uint32_t nr_mode; /* specify NR_REG_* modes */
+ uint32_t nr_extra_bufs; /* number of requested extra buffers */
+ uint64_t nr_flags; /* additional flags (see below) */
+ uint32_t nr_hdr_len; /* for PORT_HDR_SET and PORT_HDR_GET */
+ uint32_t nr_first_cpu_id; /* vale polling */
+ uint32_t nr_num_polling_cpus; /* vale polling */
+ uint32_t sync_kloop_mode; /* sync-kloop */
+ int fd; /* netmap file descriptor */
+
+ void *csb; /* CSB entries (atok and ktoa) */
+ struct nmreq_option *nr_opt; /* list of options */
+ sem_t *sem; /* for thread synchronization */
+
+ struct nmctx *nmctx;
+ const char *ifparse;
+ struct nmport_d *nmport; /* nmport descriptor from libnetmap */
+};
+
+static struct TestContext ctx_;
+
+typedef int (*testfunc_t)(struct TestContext *ctx);
+
+static void
+nmreq_hdr_init(struct nmreq_header *hdr, const char *ifname)
+{
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->nr_version = NETMAP_API;
+ assert(strlen(ifname) < NM_IFNAMSZ);
+ strncpy(hdr->nr_name, ifname, sizeof(hdr->nr_name));
+}
+
+/* Single NETMAP_REQ_PORT_INFO_GET. */
+static int
+port_info_get(struct TestContext *ctx)
+{
+ struct nmreq_port_info_get req;
+ struct nmreq_header hdr;
+ int success;
+ int ret;
+
+ printf("Testing NETMAP_REQ_PORT_INFO_GET on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_PORT_INFO_GET;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.nr_mem_id = ctx->nr_mem_id;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, PORT_INFO_GET)");
+ return ret;
+ }
+ printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
+ printf("nr_tx_slots %u\n", req.nr_tx_slots);
+ printf("nr_rx_slots %u\n", req.nr_rx_slots);
+ printf("nr_tx_rings %u\n", req.nr_tx_rings);
+ printf("nr_rx_rings %u\n", req.nr_rx_rings);
+ printf("nr_mem_id %u\n", req.nr_mem_id);
+
+ success = req.nr_memsize && req.nr_tx_slots && req.nr_rx_slots &&
+ req.nr_tx_rings && req.nr_rx_rings && req.nr_tx_rings;
+ if (!success) {
+ return -1;
+ }
+
+ /* Write back results to the context structure. */
+ ctx->nr_tx_slots = req.nr_tx_slots;
+ ctx->nr_rx_slots = req.nr_rx_slots;
+ ctx->nr_tx_rings = req.nr_tx_rings;
+ ctx->nr_rx_rings = req.nr_rx_rings;
+ ctx->nr_mem_id = req.nr_mem_id;
+
+ return 0;
+}
+
+/* Single NETMAP_REQ_REGISTER, no use. */
+static int
+port_register(struct TestContext *ctx)
+{
+ struct nmreq_register req;
+ struct nmreq_header hdr;
+ int success;
+ int ret;
+
+ printf("Testing NETMAP_REQ_REGISTER(mode=%d,ringid=%d,"
+ "flags=0x%llx) on '%s'\n",
+ ctx->nr_mode, ctx->nr_ringid, (unsigned long long)ctx->nr_flags,
+ ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_REGISTER;
+ hdr.nr_body = (uintptr_t)&req;
+ hdr.nr_options = (uintptr_t)ctx->nr_opt;
+ memset(&req, 0, sizeof(req));
+ req.nr_mem_id = ctx->nr_mem_id;
+ req.nr_mode = ctx->nr_mode;
+ req.nr_ringid = ctx->nr_ringid;
+ req.nr_flags = ctx->nr_flags;
+ req.nr_tx_slots = ctx->nr_tx_slots;
+ req.nr_rx_slots = ctx->nr_rx_slots;
+ req.nr_tx_rings = ctx->nr_tx_rings;
+ req.nr_host_tx_rings = ctx->nr_host_tx_rings;
+ req.nr_host_rx_rings = ctx->nr_host_rx_rings;
+ req.nr_rx_rings = ctx->nr_rx_rings;
+ req.nr_extra_bufs = ctx->nr_extra_bufs;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, REGISTER)");
+ return ret;
+ }
+ printf("nr_offset 0x%llx\n", (unsigned long long)req.nr_offset);
+ printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
+ printf("nr_tx_slots %u\n", req.nr_tx_slots);
+ printf("nr_rx_slots %u\n", req.nr_rx_slots);
+ printf("nr_tx_rings %u\n", req.nr_tx_rings);
+ printf("nr_rx_rings %u\n", req.nr_rx_rings);
+ printf("nr_host_tx_rings %u\n", req.nr_host_tx_rings);
+ printf("nr_host_rx_rings %u\n", req.nr_host_rx_rings);
+ printf("nr_mem_id %u\n", req.nr_mem_id);
+ printf("nr_extra_bufs %u\n", req.nr_extra_bufs);
+
+ success = req.nr_memsize && (ctx->nr_mode == req.nr_mode) &&
+ (ctx->nr_ringid == req.nr_ringid) &&
+ (ctx->nr_flags == req.nr_flags) &&
+ ((!ctx->nr_tx_slots && req.nr_tx_slots) ||
+ (ctx->nr_tx_slots == req.nr_tx_slots)) &&
+ ((!ctx->nr_rx_slots && req.nr_rx_slots) ||
+ (ctx->nr_rx_slots == req.nr_rx_slots)) &&
+ ((!ctx->nr_tx_rings && req.nr_tx_rings) ||
+ (ctx->nr_tx_rings == req.nr_tx_rings)) &&
+ ((!ctx->nr_rx_rings && req.nr_rx_rings) ||
+ (ctx->nr_rx_rings == req.nr_rx_rings)) &&
+ ((!ctx->nr_host_tx_rings && req.nr_host_tx_rings) ||
+ (ctx->nr_host_tx_rings == req.nr_host_tx_rings)) &&
+ ((!ctx->nr_host_rx_rings && req.nr_host_rx_rings) ||
+ (ctx->nr_host_rx_rings == req.nr_host_rx_rings)) &&
+ ((!ctx->nr_mem_id && req.nr_mem_id) ||
+ (ctx->nr_mem_id == req.nr_mem_id)) &&
+ (ctx->nr_extra_bufs == req.nr_extra_bufs);
+ if (!success) {
+ return -1;
+ }
+
+ /* Write back results to the context structure.*/
+ ctx->nr_tx_slots = req.nr_tx_slots;
+ ctx->nr_rx_slots = req.nr_rx_slots;
+ ctx->nr_tx_rings = req.nr_tx_rings;
+ ctx->nr_rx_rings = req.nr_rx_rings;
+ ctx->nr_host_tx_rings = req.nr_host_tx_rings;
+ ctx->nr_host_rx_rings = req.nr_host_rx_rings;
+ ctx->nr_mem_id = req.nr_mem_id;
+ ctx->nr_extra_bufs = req.nr_extra_bufs;
+
+ return 0;
+}
+
+static int
+niocregif(struct TestContext *ctx, int netmap_api)
+{
+ struct nmreq req;
+ int success;
+ int ret;
+
+ printf("Testing legacy NIOCREGIF on '%s'\n", ctx->ifname_ext);
+
+ memset(&req, 0, sizeof(req));
+ memcpy(req.nr_name, ctx->ifname_ext, sizeof(req.nr_name));
+ req.nr_name[sizeof(req.nr_name) - 1] = '\0';
+ req.nr_version = netmap_api;
+ req.nr_ringid = ctx->nr_ringid;
+ req.nr_flags = ctx->nr_mode | ctx->nr_flags;
+ req.nr_tx_slots = ctx->nr_tx_slots;
+ req.nr_rx_slots = ctx->nr_rx_slots;
+ req.nr_tx_rings = ctx->nr_tx_rings;
+ req.nr_rx_rings = ctx->nr_rx_rings;
+ req.nr_arg2 = ctx->nr_mem_id;
+ req.nr_arg3 = ctx->nr_extra_bufs;
+
+ ret = ioctl(ctx->fd, NIOCREGIF, &req);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCREGIF)");
+ return ret;
+ }
+
+ printf("nr_offset 0x%x\n", req.nr_offset);
+ printf("nr_memsize %u\n", req.nr_memsize);
+ printf("nr_tx_slots %u\n", req.nr_tx_slots);
+ printf("nr_rx_slots %u\n", req.nr_rx_slots);
+ printf("nr_tx_rings %u\n", req.nr_tx_rings);
+ printf("nr_rx_rings %u\n", req.nr_rx_rings);
+ printf("nr_version %d\n", req.nr_version);
+ printf("nr_ringid %x\n", req.nr_ringid);
+ printf("nr_flags %x\n", req.nr_flags);
+ printf("nr_arg2 %u\n", req.nr_arg2);
+ printf("nr_arg3 %u\n", req.nr_arg3);
+
+ success = req.nr_memsize &&
+ (ctx->nr_ringid == req.nr_ringid) &&
+ ((ctx->nr_mode | ctx->nr_flags) == req.nr_flags) &&
+ ((!ctx->nr_tx_slots && req.nr_tx_slots) ||
+ (ctx->nr_tx_slots == req.nr_tx_slots)) &&
+ ((!ctx->nr_rx_slots && req.nr_rx_slots) ||
+ (ctx->nr_rx_slots == req.nr_rx_slots)) &&
+ ((!ctx->nr_tx_rings && req.nr_tx_rings) ||
+ (ctx->nr_tx_rings == req.nr_tx_rings)) &&
+ ((!ctx->nr_rx_rings && req.nr_rx_rings) ||
+ (ctx->nr_rx_rings == req.nr_rx_rings)) &&
+ ((!ctx->nr_mem_id && req.nr_arg2) ||
+ (ctx->nr_mem_id == req.nr_arg2)) &&
+ (ctx->nr_extra_bufs == req.nr_arg3);
+ if (!success) {
+ return -1;
+ }
+
+ /* Write back results to the context structure.*/
+ ctx->nr_tx_slots = req.nr_tx_slots;
+ ctx->nr_rx_slots = req.nr_rx_slots;
+ ctx->nr_tx_rings = req.nr_tx_rings;
+ ctx->nr_rx_rings = req.nr_rx_rings;
+ ctx->nr_mem_id = req.nr_arg2;
+ ctx->nr_extra_bufs = req.nr_arg3;
+
+ return ret;
+}
+
+/* The 11 ABI is the one right before the introduction of the new NIOCCTRL
+ * ABI. The 11 ABI is useful to perform tests with legacy applications
+ * (which use the 11 ABI) and new kernel (which uses 12, or higher).
+ * However, version 14 introduced a change in the layout of struct netmap_if,
+ * so that binary backward compatibility to 11 is not supported anymore.
+ */
+#define NETMAP_API_NIOCREGIF 14
+
+static int
+legacy_regif_default(struct TestContext *ctx)
+{
+ return niocregif(ctx, NETMAP_API_NIOCREGIF);
+}
+
+static int
+legacy_regif_all_nic(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ return niocregif(ctx, NETMAP_API);
+}
+
+static int
+legacy_regif_12(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ return niocregif(ctx, NETMAP_API_NIOCREGIF+1);
+}
+
+static int
+legacy_regif_sw(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_SW;
+ return niocregif(ctx, NETMAP_API_NIOCREGIF);
+}
+
+static int
+legacy_regif_future(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_NIC_SW;
+ /* Test forward compatibility for the legacy ABI. This means
+ * using an older kernel (with ABI 12 or higher) and a newer
+ * application (with ABI greater than NETMAP_API). */
+ return niocregif(ctx, NETMAP_API+2);
+}
+
+static int
+legacy_regif_extra_bufs(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ ctx->nr_extra_bufs = 20; /* arbitrary number of extra bufs */
+ return niocregif(ctx, NETMAP_API_NIOCREGIF);
+}
+
+static int
+legacy_regif_extra_bufs_pipe(struct TestContext *ctx)
+{
+ strncat(ctx->ifname_ext, "{pipeexbuf", sizeof(ctx->ifname_ext));
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ ctx->nr_extra_bufs = 58; /* arbitrary number of extra bufs */
+
+ return niocregif(ctx, NETMAP_API_NIOCREGIF);
+}
+
+static int
+legacy_regif_extra_bufs_pipe_vale(struct TestContext *ctx)
+{
+ strncpy(ctx->ifname_ext, "valeX1:Y4", sizeof(ctx->ifname_ext));
+ return legacy_regif_extra_bufs_pipe(ctx);
+}
+
+/* Only valid after a successful port_register(). */
+static int
+num_registered_rings(struct TestContext *ctx)
+{
+ if (ctx->nr_flags & NR_TX_RINGS_ONLY) {
+ return ctx->nr_tx_rings;
+ }
+ if (ctx->nr_flags & NR_RX_RINGS_ONLY) {
+ return ctx->nr_rx_rings;
+ }
+
+ return ctx->nr_tx_rings + ctx->nr_rx_rings;
+}
+
+static int
+port_register_hwall_host(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_NIC_SW;
+ return port_register(ctx);
+}
+
+static int
+port_register_hostall(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_SW;
+ return port_register(ctx);
+}
+
+static int
+port_register_hwall(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ return port_register(ctx);
+}
+
+static int
+port_register_single_hw_pair(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ONE_NIC;
+ ctx->nr_ringid = 0;
+ return port_register(ctx);
+}
+
+static int
+port_register_single_host_pair(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ONE_SW;
+ ctx->nr_host_tx_rings = 2;
+ ctx->nr_host_rx_rings = 2;
+ ctx->nr_ringid = 1;
+ return port_register(ctx);
+}
+
+static int
+port_register_hostall_many(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_SW;
+ ctx->nr_host_tx_rings = 5;
+ ctx->nr_host_rx_rings = 4;
+ return port_register(ctx);
+}
+
+static int
+port_register_hwall_tx(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ ctx->nr_flags |= NR_TX_RINGS_ONLY;
+ return port_register(ctx);
+}
+
+static int
+port_register_hwall_rx(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ ctx->nr_flags |= NR_RX_RINGS_ONLY;
+ return port_register(ctx);
+}
+
+
+static int
+vale_mkname(char *vpname, struct TestContext *ctx)
+{
+ if (snprintf(vpname, NM_IFNAMSZ, "%s:%s", ctx->bdgname, ctx->ifname_ext) >= NM_IFNAMSZ) {
+ fprintf(stderr, "%s:%s too long (max %d chars)\n", ctx->bdgname, ctx->ifname_ext,
+ NM_IFNAMSZ - 1);
+ return -1;
+ }
+ return 0;
+}
+
+
+/* NETMAP_REQ_VALE_ATTACH */
+static int
+vale_attach(struct TestContext *ctx)
+{
+ struct nmreq_vale_attach req;
+ struct nmreq_header hdr;
+ char vpname[NM_IFNAMSZ];
+ int ret;
+
+ if (vale_mkname(vpname, ctx) < 0)
+ return -1;
+
+ printf("Testing NETMAP_REQ_VALE_ATTACH on '%s'\n", vpname);
+ nmreq_hdr_init(&hdr, vpname);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_ATTACH;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.reg.nr_mem_id = ctx->nr_mem_id;
+ if (ctx->nr_mode == 0) {
+ ctx->nr_mode = NR_REG_ALL_NIC; /* default */
+ }
+ req.reg.nr_mode = ctx->nr_mode;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_ATTACH)");
+ return ret;
+ }
+ printf("nr_mem_id %u\n", req.reg.nr_mem_id);
+
+ return ((!ctx->nr_mem_id && req.reg.nr_mem_id > 1) ||
+ (ctx->nr_mem_id == req.reg.nr_mem_id)) &&
+ (ctx->nr_flags == req.reg.nr_flags)
+ ? 0
+ : -1;
+}
+
+/* NETMAP_REQ_VALE_DETACH */
+static int
+vale_detach(struct TestContext *ctx)
+{
+ struct nmreq_header hdr;
+ struct nmreq_vale_detach req;
+ char vpname[NM_IFNAMSZ];
+ int ret;
+
+ if (vale_mkname(vpname, ctx) < 0)
+ return -1;
+
+ printf("Testing NETMAP_REQ_VALE_DETACH on '%s'\n", vpname);
+ nmreq_hdr_init(&hdr, vpname);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_DETACH;
+ hdr.nr_body = (uintptr_t)&req;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_DETACH)");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* First NETMAP_REQ_VALE_ATTACH, then NETMAP_REQ_VALE_DETACH. */
+static int
+vale_attach_detach(struct TestContext *ctx)
+{
+ int ret;
+
+ if ((ret = vale_attach(ctx)) != 0) {
+ return ret;
+ }
+
+ return vale_detach(ctx);
+}
+
+static int
+vale_attach_detach_host_rings(struct TestContext *ctx)
+{
+ ctx->nr_mode = NR_REG_NIC_SW;
+ return vale_attach_detach(ctx);
+}
+
+/* First NETMAP_REQ_PORT_HDR_SET and the NETMAP_REQ_PORT_HDR_GET
+ * to check that we get the same value. */
+static int
+port_hdr_set_and_get(struct TestContext *ctx)
+{
+ struct nmreq_port_hdr req;
+ struct nmreq_header hdr;
+ int ret;
+
+ printf("Testing NETMAP_REQ_PORT_HDR_SET on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_PORT_HDR_SET;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.nr_hdr_len = ctx->nr_hdr_len;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, PORT_HDR_SET)");
+ return ret;
+ }
+
+ if (req.nr_hdr_len != ctx->nr_hdr_len) {
+ return -1;
+ }
+
+ printf("Testing NETMAP_REQ_PORT_HDR_GET on '%s'\n", ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_PORT_HDR_GET;
+ req.nr_hdr_len = 0;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, PORT_HDR_SET)");
+ return ret;
+ }
+ printf("nr_hdr_len %u\n", req.nr_hdr_len);
+
+ return (req.nr_hdr_len == ctx->nr_hdr_len) ? 0 : -1;
+}
+
+/*
+ * Possible lengths for the VirtIO network header, as specified by
+ * the standard:
+ * http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html
+ */
+#define VIRTIO_NET_HDR_LEN 10
+#define VIRTIO_NET_HDR_LEN_WITH_MERGEABLE_RXBUFS 12
+
+static int
+vale_ephemeral_port_hdr_manipulation(struct TestContext *ctx)
+{
+ int ret;
+
+ strncpy(ctx->ifname_ext, "vale:eph0", sizeof(ctx->ifname_ext));
+ ctx->nr_mode = NR_REG_ALL_NIC;
+ if ((ret = port_register(ctx))) {
+ return ret;
+ }
+ /* Try to set and get all the acceptable values. */
+ ctx->nr_hdr_len = VIRTIO_NET_HDR_LEN_WITH_MERGEABLE_RXBUFS;
+ if ((ret = port_hdr_set_and_get(ctx))) {
+ return ret;
+ }
+ ctx->nr_hdr_len = 0;
+ if ((ret = port_hdr_set_and_get(ctx))) {
+ return ret;
+ }
+ ctx->nr_hdr_len = VIRTIO_NET_HDR_LEN;
+ if ((ret = port_hdr_set_and_get(ctx))) {
+ return ret;
+ }
+ return 0;
+}
+
+static int
+vale_persistent_port(struct TestContext *ctx)
+{
+ struct nmreq_vale_newif req;
+ struct nmreq_header hdr;
+ int result;
+ int ret;
+
+ strncpy(ctx->ifname_ext, "per4", sizeof(ctx->ifname_ext));
+
+ printf("Testing NETMAP_REQ_VALE_NEWIF on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_NEWIF;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.nr_mem_id = ctx->nr_mem_id;
+ req.nr_tx_slots = ctx->nr_tx_slots;
+ req.nr_rx_slots = ctx->nr_rx_slots;
+ req.nr_tx_rings = ctx->nr_tx_rings;
+ req.nr_rx_rings = ctx->nr_rx_rings;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_NEWIF)");
+ return ret;
+ }
+
+ /* Attach the persistent VALE port to a switch and then detach. */
+ result = vale_attach_detach(ctx);
+
+ printf("Testing NETMAP_REQ_VALE_DELIF on '%s'\n", ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_DELIF;
+ hdr.nr_body = (uintptr_t)NULL;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_NEWIF)");
+ if (result == 0) {
+ result = ret;
+ }
+ }
+
+ return result;
+}
+
+/* Single NETMAP_REQ_POOLS_INFO_GET. */
+static int
+pools_info_get(struct TestContext *ctx)
+{
+ struct nmreq_pools_info req;
+ struct nmreq_header hdr;
+ int ret;
+
+ printf("Testing NETMAP_REQ_POOLS_INFO_GET on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_POOLS_INFO_GET;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.nr_mem_id = ctx->nr_mem_id;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, POOLS_INFO_GET)");
+ return ret;
+ }
+ printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
+ printf("nr_mem_id %u\n", req.nr_mem_id);
+ printf("nr_if_pool_offset 0x%llx\n",
+ (unsigned long long)req.nr_if_pool_offset);
+ printf("nr_if_pool_objtotal %u\n", req.nr_if_pool_objtotal);
+ printf("nr_if_pool_objsize %u\n", req.nr_if_pool_objsize);
+ printf("nr_ring_pool_offset 0x%llx\n",
+ (unsigned long long)req.nr_if_pool_offset);
+ printf("nr_ring_pool_objtotal %u\n", req.nr_ring_pool_objtotal);
+ printf("nr_ring_pool_objsize %u\n", req.nr_ring_pool_objsize);
+ printf("nr_buf_pool_offset 0x%llx\n",
+ (unsigned long long)req.nr_buf_pool_offset);
+ printf("nr_buf_pool_objtotal %u\n", req.nr_buf_pool_objtotal);
+ printf("nr_buf_pool_objsize %u\n", req.nr_buf_pool_objsize);
+
+ return req.nr_memsize && req.nr_if_pool_objtotal &&
+ req.nr_if_pool_objsize &&
+ req.nr_ring_pool_objtotal &&
+ req.nr_ring_pool_objsize &&
+ req.nr_buf_pool_objtotal &&
+ req.nr_buf_pool_objsize
+ ? 0
+ : -1;
+}
+
+static int
+pools_info_get_and_register(struct TestContext *ctx)
+{
+ int ret;
+
+ /* Check that we can get pools info before we register
+ * a netmap interface. */
+ ret = pools_info_get(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ctx->nr_mode = NR_REG_ONE_NIC;
+ ret = port_register(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ ctx->nr_mem_id = 1;
+
+ /* Check that we can get pools info also after we register. */
+ return pools_info_get(ctx);
+}
+
+static int
+pools_info_get_empty_ifname(struct TestContext *ctx)
+{
+ strncpy(ctx->ifname_ext, "", sizeof(ctx->ifname_ext));
+ return pools_info_get(ctx) != 0 ? 0 : -1;
+}
+
+static int
+pipe_master(struct TestContext *ctx)
+{
+ strncat(ctx->ifname_ext, "{pipeid1", sizeof(ctx->ifname_ext));
+ ctx->nr_mode = NR_REG_NIC_SW;
+
+ if (port_register(ctx) == 0) {
+ printf("pipes should not accept NR_REG_NIC_SW\n");
+ return -1;
+ }
+ ctx->nr_mode = NR_REG_ALL_NIC;
+
+ return port_register(ctx);
+}
+
+static int
+pipe_slave(struct TestContext *ctx)
+{
+ strncat(ctx->ifname_ext, "}pipeid2", sizeof(ctx->ifname_ext));
+ ctx->nr_mode = NR_REG_ALL_NIC;
+
+ return port_register(ctx);
+}
+
+/* Test PORT_INFO_GET and POOLS_INFO_GET on a pipe. This is useful to test the
+ * registration request used internally by netmap. */
+static int
+pipe_port_info_get(struct TestContext *ctx)
+{
+ strncat(ctx->ifname_ext, "}pipeid3", sizeof(ctx->ifname_ext));
+
+ return port_info_get(ctx);
+}
+
+static int
+pipe_pools_info_get(struct TestContext *ctx)
+{
+ strncat(ctx->ifname_ext, "{xid", sizeof(ctx->ifname_ext));
+
+ return pools_info_get(ctx);
+}
+
+/* NETMAP_REQ_VALE_POLLING_ENABLE */
+static int
+vale_polling_enable(struct TestContext *ctx)
+{
+ struct nmreq_vale_polling req;
+ struct nmreq_header hdr;
+ char vpname[NM_IFNAMSZ];
+ int ret;
+
+ if (vale_mkname(vpname, ctx) < 0)
+ return -1;
+
+ printf("Testing NETMAP_REQ_VALE_POLLING_ENABLE on '%s'\n", vpname);
+
+ nmreq_hdr_init(&hdr, vpname);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_POLLING_ENABLE;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ req.nr_mode = ctx->nr_mode;
+ req.nr_first_cpu_id = ctx->nr_first_cpu_id;
+ req.nr_num_polling_cpus = ctx->nr_num_polling_cpus;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_POLLING_ENABLE)");
+ return ret;
+ }
+
+ return (req.nr_mode == ctx->nr_mode &&
+ req.nr_first_cpu_id == ctx->nr_first_cpu_id &&
+ req.nr_num_polling_cpus == ctx->nr_num_polling_cpus)
+ ? 0
+ : -1;
+}
+
+/* NETMAP_REQ_VALE_POLLING_DISABLE */
+static int
+vale_polling_disable(struct TestContext *ctx)
+{
+ struct nmreq_vale_polling req;
+ struct nmreq_header hdr;
+ char vpname[NM_IFNAMSZ];
+ int ret;
+
+ if (vale_mkname(vpname, ctx) < 0)
+ return -1;
+
+ printf("Testing NETMAP_REQ_VALE_POLLING_DISABLE on '%s'\n", vpname);
+
+ nmreq_hdr_init(&hdr, vpname);
+ hdr.nr_reqtype = NETMAP_REQ_VALE_POLLING_DISABLE;
+ hdr.nr_body = (uintptr_t)&req;
+ memset(&req, 0, sizeof(req));
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, VALE_POLLING_DISABLE)");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+vale_polling_enable_disable(struct TestContext *ctx)
+{
+ int ret = 0;
+
+ if ((ret = vale_attach(ctx)) != 0) {
+ return ret;
+ }
+
+ ctx->nr_mode = NETMAP_POLLING_MODE_SINGLE_CPU;
+ ctx->nr_num_polling_cpus = 1;
+ ctx->nr_first_cpu_id = 0;
+ if ((ret = vale_polling_enable(ctx))) {
+ vale_detach(ctx);
+#ifdef __FreeBSD__
+ /* NETMAP_REQ_VALE_POLLING_DISABLE is disabled on FreeBSD,
+ * because it is currently broken. We are happy to see that
+ * it fails. */
+ return 0;
+#else
+ return ret;
+#endif
+ }
+
+ if ((ret = vale_polling_disable(ctx))) {
+ vale_detach(ctx);
+ return ret;
+ }
+
+ return vale_detach(ctx);
+}
+
+static void
+push_option(struct nmreq_option *opt, struct TestContext *ctx)
+{
+ opt->nro_next = (uintptr_t)ctx->nr_opt;
+ ctx->nr_opt = opt;
+}
+
+static void
+clear_options(struct TestContext *ctx)
+{
+ ctx->nr_opt = NULL;
+}
+
+static int
+checkoption(struct nmreq_option *opt, struct nmreq_option *exp)
+{
+ if (opt->nro_next != exp->nro_next) {
+ printf("nro_next %p expected %p\n",
+ (void *)(uintptr_t)opt->nro_next,
+ (void *)(uintptr_t)exp->nro_next);
+ return -1;
+ }
+ if (opt->nro_reqtype != exp->nro_reqtype) {
+ printf("nro_reqtype %u expected %u\n", opt->nro_reqtype,
+ exp->nro_reqtype);
+ return -1;
+ }
+ if (opt->nro_status != exp->nro_status) {
+ printf("nro_status %u expected %u\n", opt->nro_status,
+ exp->nro_status);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+unsupported_option(struct TestContext *ctx)
+{
+ struct nmreq_option opt, save;
+
+ printf("Testing unsupported option on %s\n", ctx->ifname_ext);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.nro_reqtype = 1234;
+ push_option(&opt, ctx);
+ save = opt;
+
+ if (port_register_hwall(ctx) >= 0)
+ return -1;
+
+ clear_options(ctx);
+ save.nro_status = EOPNOTSUPP;
+ return checkoption(&opt, &save);
+}
+
+static int
+infinite_options(struct TestContext *ctx)
+{
+ struct nmreq_option opt;
+
+ printf("Testing infinite list of options on %s (invalid options)\n", ctx->ifname_ext);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.nro_reqtype = NETMAP_REQ_OPT_MAX + 1;
+ push_option(&opt, ctx);
+ opt.nro_next = (uintptr_t)&opt;
+ if (port_register_hwall(ctx) >= 0)
+ return -1;
+ clear_options(ctx);
+ return (errno == EMSGSIZE ? 0 : -1);
+}
+
+static int
+infinite_options2(struct TestContext *ctx)
+{
+ struct nmreq_option opt;
+
+ printf("Testing infinite list of options on %s (valid options)\n", ctx->ifname_ext);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.nro_reqtype = NETMAP_REQ_OPT_OFFSETS;
+ push_option(&opt, ctx);
+ opt.nro_next = (uintptr_t)&opt;
+ if (port_register_hwall(ctx) >= 0)
+ return -1;
+ clear_options(ctx);
+ return (errno == EINVAL ? 0 : -1);
+}
+
+#ifdef CONFIG_NETMAP_EXTMEM
+int
+change_param(const char *pname, unsigned long newv, unsigned long *poldv)
+{
+#ifdef __linux__
+ char param[256] = "/sys/module/netmap/parameters/";
+ unsigned long oldv;
+ FILE *f;
+
+ strncat(param, pname, sizeof(param) - 1);
+
+ f = fopen(param, "r+");
+ if (f == NULL) {
+ perror(param);
+ return -1;
+ }
+ if (fscanf(f, "%ld", &oldv) != 1) {
+ perror(param);
+ fclose(f);
+ return -1;
+ }
+ if (poldv)
+ *poldv = oldv;
+ rewind(f);
+ if (fprintf(f, "%ld\n", newv) < 0) {
+ perror(param);
+ fclose(f);
+ return -1;
+ }
+ fclose(f);
+ printf("change_param: %s: %ld -> %ld\n", pname, oldv, newv);
+#endif /* __linux__ */
+ return 0;
+}
+
+static int
+push_extmem_option(struct TestContext *ctx, const struct nmreq_pools_info *pi,
+ struct nmreq_opt_extmem *e)
+{
+ void *addr;
+
+ addr = mmap(NULL, pi->nr_memsize, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+ if (addr == MAP_FAILED) {
+ perror("mmap");
+ return -1;
+ }
+
+ memset(e, 0, sizeof(*e));
+ e->nro_opt.nro_reqtype = NETMAP_REQ_OPT_EXTMEM;
+ e->nro_info = *pi;
+ e->nro_usrptr = (uintptr_t)addr;
+
+ push_option(&e->nro_opt, ctx);
+
+ return 0;
+}
+
+static int
+pop_extmem_option(struct TestContext *ctx, struct nmreq_opt_extmem *exp)
+{
+ struct nmreq_opt_extmem *e;
+ int ret;
+
+ e = (struct nmreq_opt_extmem *)(uintptr_t)ctx->nr_opt;
+ ctx->nr_opt = (struct nmreq_option *)(uintptr_t)ctx->nr_opt->nro_next;
+
+ if ((ret = checkoption(&e->nro_opt, &exp->nro_opt))) {
+ return ret;
+ }
+
+ if (e->nro_usrptr != exp->nro_usrptr) {
+ printf("usrptr %" PRIu64 " expected %" PRIu64 "\n",
+ e->nro_usrptr, exp->nro_usrptr);
+ return -1;
+ }
+ if (e->nro_info.nr_memsize != exp->nro_info.nr_memsize) {
+ printf("memsize %" PRIu64 " expected %" PRIu64 "\n",
+ e->nro_info.nr_memsize, exp->nro_info.nr_memsize);
+ return -1;
+ }
+
+ if ((ret = munmap((void *)(uintptr_t)e->nro_usrptr,
+ e->nro_info.nr_memsize)))
+ return ret;
+
+ return 0;
+}
+
+static int
+_extmem_option(struct TestContext *ctx,
+ const struct nmreq_pools_info *pi)
+{
+ struct nmreq_opt_extmem e, save;
+ int ret;
+
+ if ((ret = push_extmem_option(ctx, pi, &e)) < 0)
+ return ret;
+
+ save = e;
+
+ strncpy(ctx->ifname_ext, "vale0:0", sizeof(ctx->ifname_ext));
+ ctx->nr_tx_slots = 16;
+ ctx->nr_rx_slots = 16;
+
+ if ((ret = port_register_hwall(ctx)))
+ return ret;
+
+ ret = pop_extmem_option(ctx, &save);
+
+ return ret;
+}
+
+static size_t
+pools_info_min_memsize(const struct nmreq_pools_info *pi)
+{
+ size_t tot = 0;
+
+ tot += pi->nr_if_pool_objtotal * pi->nr_if_pool_objsize;
+ tot += pi->nr_ring_pool_objtotal * pi->nr_ring_pool_objsize;
+ tot += pi->nr_buf_pool_objtotal * pi->nr_buf_pool_objsize;
+
+ return tot;
+}
+
+/*
+ * Fill the specification of a netmap memory allocator to be
+ * used with the 'struct nmreq_opt_extmem' option. Arbitrary
+ * values are used for the parameters, but with enough netmap
+ * rings, netmap ifs, and buffers to support a VALE port.
+ */
+static void
+pools_info_fill(struct nmreq_pools_info *pi)
+{
+ pi->nr_if_pool_objtotal = 2;
+ pi->nr_if_pool_objsize = 1024;
+ pi->nr_ring_pool_objtotal = 64;
+ pi->nr_ring_pool_objsize = 512;
+ pi->nr_buf_pool_objtotal = 4096;
+ pi->nr_buf_pool_objsize = 2048;
+ pi->nr_memsize = pools_info_min_memsize(pi);
+}
+
+static int
+extmem_option(struct TestContext *ctx)
+{
+ struct nmreq_pools_info pools_info;
+
+ pools_info_fill(&pools_info);
+
+ printf("Testing extmem option on vale0:0\n");
+ return _extmem_option(ctx, &pools_info);
+}
+
+static int
+bad_extmem_option(struct TestContext *ctx)
+{
+ struct nmreq_pools_info pools_info;
+
+ printf("Testing bad extmem option on vale0:0\n");
+
+ pools_info_fill(&pools_info);
+ /* Request a large ring size, to make sure that the kernel
+ * rejects our request. */
+ pools_info.nr_ring_pool_objsize = (1 << 20);
+
+ return _extmem_option(ctx, &pools_info) < 0 ? 0 : -1;
+}
+
+static int
+duplicate_extmem_options(struct TestContext *ctx)
+{
+ struct nmreq_opt_extmem e1, save1, e2, save2;
+ struct nmreq_pools_info pools_info;
+ int ret;
+
+ printf("Testing duplicate extmem option on vale0:0\n");
+
+ pools_info_fill(&pools_info);
+
+ if ((ret = push_extmem_option(ctx, &pools_info, &e1)) < 0)
+ return ret;
+
+ if ((ret = push_extmem_option(ctx, &pools_info, &e2)) < 0) {
+ clear_options(ctx);
+ return ret;
+ }
+
+ save1 = e1;
+ save2 = e2;
+
+ strncpy(ctx->ifname_ext, "vale0:0", sizeof(ctx->ifname_ext));
+ ctx->nr_tx_slots = 16;
+ ctx->nr_rx_slots = 16;
+
+ ret = port_register_hwall(ctx);
+ if (ret >= 0) {
+ printf("duplicate option not detected\n");
+ return -1;
+ }
+
+ save2.nro_opt.nro_status = EINVAL;
+ if ((ret = pop_extmem_option(ctx, &save2)))
+ return ret;
+
+ save1.nro_opt.nro_status = EINVAL;
+ if ((ret = pop_extmem_option(ctx, &save1)))
+ return ret;
+
+ return 0;
+}
+#endif /* CONFIG_NETMAP_EXTMEM */
+
+static int
+push_csb_option(struct TestContext *ctx, struct nmreq_opt_csb *opt)
+{
+ size_t csb_size;
+ int num_entries;
+ int ret;
+
+ ctx->nr_flags |= NR_EXCLUSIVE;
+
+ /* Get port info in order to use num_registered_rings(). */
+ ret = port_info_get(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ num_entries = num_registered_rings(ctx);
+
+ csb_size = (sizeof(struct nm_csb_atok) + sizeof(struct nm_csb_ktoa)) *
+ num_entries;
+ assert(csb_size > 0);
+ if (ctx->csb) {
+ free(ctx->csb);
+ }
+ ret = posix_memalign(&ctx->csb, sizeof(struct nm_csb_atok), csb_size);
+ if (ret != 0) {
+ printf("Failed to allocate CSB memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(opt, 0, sizeof(*opt));
+ opt->nro_opt.nro_reqtype = NETMAP_REQ_OPT_CSB;
+ opt->csb_atok = (uintptr_t)ctx->csb;
+ opt->csb_ktoa = (uintptr_t)(((uint8_t *)ctx->csb) +
+ sizeof(struct nm_csb_atok) * num_entries);
+
+ printf("Pushing option NETMAP_REQ_OPT_CSB\n");
+ push_option(&opt->nro_opt, ctx);
+
+ return 0;
+}
+
+static int
+csb_mode(struct TestContext *ctx)
+{
+ struct nmreq_opt_csb opt;
+ int ret;
+
+ ret = push_csb_option(ctx, &opt);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = port_register_hwall(ctx);
+ clear_options(ctx);
+
+ return ret;
+}
+
+static int
+csb_mode_invalid_memory(struct TestContext *ctx)
+{
+ struct nmreq_opt_csb opt;
+ int ret;
+
+ memset(&opt, 0, sizeof(opt));
+ opt.nro_opt.nro_reqtype = NETMAP_REQ_OPT_CSB;
+ opt.csb_atok = (uintptr_t)0x10;
+ opt.csb_ktoa = (uintptr_t)0x800;
+ push_option(&opt.nro_opt, ctx);
+
+ ctx->nr_flags = NR_EXCLUSIVE;
+ ret = port_register_hwall(ctx);
+ clear_options(ctx);
+
+ return (ret < 0) ? 0 : -1;
+}
+
+static int
+sync_kloop_stop(struct TestContext *ctx)
+{
+ struct nmreq_header hdr;
+ int ret;
+
+ printf("Testing NETMAP_REQ_SYNC_KLOOP_STOP on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_SYNC_KLOOP_STOP;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, SYNC_KLOOP_STOP)");
+ }
+
+ return ret;
+}
+
+static void *
+sync_kloop_worker(void *opaque)
+{
+ struct TestContext *ctx = opaque;
+ struct nmreq_sync_kloop_start req;
+ struct nmreq_header hdr;
+ int ret;
+
+ printf("Testing NETMAP_REQ_SYNC_KLOOP_START on '%s'\n", ctx->ifname_ext);
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_SYNC_KLOOP_START;
+ hdr.nr_body = (uintptr_t)&req;
+ hdr.nr_options = (uintptr_t)ctx->nr_opt;
+ memset(&req, 0, sizeof(req));
+ req.sleep_us = 500;
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, SYNC_KLOOP_START)");
+ }
+
+ if (ctx->sem) {
+ sem_post(ctx->sem);
+ }
+
+ pthread_exit(ret ? (void *)THRET_FAILURE : (void *)THRET_SUCCESS);
+}
+
+static int
+sync_kloop_start_stop(struct TestContext *ctx)
+{
+ pthread_t th;
+ void *thret = THRET_FAILURE;
+ int ret;
+
+ ret = pthread_create(&th, NULL, sync_kloop_worker, ctx);
+ if (ret != 0) {
+ printf("pthread_create(kloop): %s\n", strerror(ret));
+ return -1;
+ }
+
+ ret = sync_kloop_stop(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = pthread_join(th, &thret);
+ if (ret != 0) {
+ printf("pthread_join(kloop): %s\n", strerror(ret));
+ }
+
+ return thret == THRET_SUCCESS ? 0 : -1;
+}
+
+static int
+sync_kloop(struct TestContext *ctx)
+{
+ int ret;
+
+ ret = csb_mode(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return sync_kloop_start_stop(ctx);
+}
+
+static int
+sync_kloop_eventfds(struct TestContext *ctx)
+{
+ struct nmreq_opt_sync_kloop_eventfds *evopt = NULL;
+ struct nmreq_opt_sync_kloop_mode modeopt;
+ struct nmreq_option evsave;
+ int num_entries;
+ size_t opt_size;
+ int ret, i;
+
+ memset(&modeopt, 0, sizeof(modeopt));
+ modeopt.nro_opt.nro_reqtype = NETMAP_REQ_OPT_SYNC_KLOOP_MODE;
+ modeopt.mode = ctx->sync_kloop_mode;
+ push_option(&modeopt.nro_opt, ctx);
+
+ num_entries = num_registered_rings(ctx);
+ opt_size = sizeof(*evopt) + num_entries * sizeof(evopt->eventfds[0]);
+ evopt = calloc(1, opt_size);
+ evopt->nro_opt.nro_next = 0;
+ evopt->nro_opt.nro_reqtype = NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS;
+ evopt->nro_opt.nro_status = 0;
+ evopt->nro_opt.nro_size = opt_size;
+ for (i = 0; i < num_entries; i++) {
+ int efd = eventfd(0, 0);
+
+ evopt->eventfds[i].ioeventfd = efd;
+ efd = eventfd(0, 0);
+ evopt->eventfds[i].irqfd = efd;
+ }
+
+ push_option(&evopt->nro_opt, ctx);
+ evsave = evopt->nro_opt;
+
+ ret = sync_kloop_start_stop(ctx);
+ if (ret != 0) {
+ free(evopt);
+ clear_options(ctx);
+ return ret;
+ }
+#ifdef __linux__
+ evsave.nro_status = 0;
+#else /* !__linux__ */
+ evsave.nro_status = EOPNOTSUPP;
+#endif /* !__linux__ */
+
+ ret = checkoption(&evopt->nro_opt, &evsave);
+ free(evopt);
+ clear_options(ctx);
+
+ return ret;
+}
+
+static int
+sync_kloop_eventfds_all_mode(struct TestContext *ctx,
+ uint32_t sync_kloop_mode)
+{
+ int ret;
+
+ ret = csb_mode(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ctx->sync_kloop_mode = sync_kloop_mode;
+
+ return sync_kloop_eventfds(ctx);
+}
+
+static int
+sync_kloop_eventfds_all(struct TestContext *ctx)
+{
+ return sync_kloop_eventfds_all_mode(ctx, 0);
+}
+
+static int
+sync_kloop_eventfds_all_tx(struct TestContext *ctx)
+{
+ struct nmreq_opt_csb opt;
+ int ret;
+
+ ret = push_csb_option(ctx, &opt);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = port_register_hwall_tx(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ clear_options(ctx);
+
+ return sync_kloop_eventfds(ctx);
+}
+
+static int
+sync_kloop_eventfds_all_direct(struct TestContext *ctx)
+{
+ return sync_kloop_eventfds_all_mode(ctx,
+ NM_OPT_SYNC_KLOOP_DIRECT_TX | NM_OPT_SYNC_KLOOP_DIRECT_RX);
+}
+
+static int
+sync_kloop_eventfds_all_direct_tx(struct TestContext *ctx)
+{
+ return sync_kloop_eventfds_all_mode(ctx,
+ NM_OPT_SYNC_KLOOP_DIRECT_TX);
+}
+
+static int
+sync_kloop_eventfds_all_direct_rx(struct TestContext *ctx)
+{
+ return sync_kloop_eventfds_all_mode(ctx,
+ NM_OPT_SYNC_KLOOP_DIRECT_RX);
+}
+
+static int
+sync_kloop_nocsb(struct TestContext *ctx)
+{
+ int ret;
+
+ ret = port_register_hwall(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Sync kloop must fail because we did not use
+ * NETMAP_REQ_CSB_ENABLE. */
+ return sync_kloop_start_stop(ctx) != 0 ? 0 : -1;
+}
+
+static int
+csb_enable(struct TestContext *ctx)
+{
+ struct nmreq_option saveopt;
+ struct nmreq_opt_csb opt;
+ struct nmreq_header hdr;
+ int ret;
+
+ ret = push_csb_option(ctx, &opt);
+ if (ret != 0) {
+ return ret;
+ }
+ saveopt = opt.nro_opt;
+ saveopt.nro_status = 0;
+
+ nmreq_hdr_init(&hdr, ctx->ifname_ext);
+ hdr.nr_reqtype = NETMAP_REQ_CSB_ENABLE;
+ hdr.nr_options = (uintptr_t)ctx->nr_opt;
+ hdr.nr_body = (uintptr_t)NULL;
+
+ printf("Testing NETMAP_REQ_CSB_ENABLE on '%s'\n", ctx->ifname_ext);
+
+ ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
+ if (ret != 0) {
+ perror("ioctl(/dev/netmap, NIOCCTRL, CSB_ENABLE)");
+ return ret;
+ }
+
+ ret = checkoption(&opt.nro_opt, &saveopt);
+ clear_options(ctx);
+
+ return ret;
+}
+
+static int
+sync_kloop_csb_enable(struct TestContext *ctx)
+{
+ int ret;
+
+ ctx->nr_flags |= NR_EXCLUSIVE;
+ ret = port_register_hwall(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = csb_enable(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return sync_kloop_start_stop(ctx);
+}
+
+#if 0
+static int
+sync_kloop_conflict(struct TestContext *ctx)
+{
+ struct nmreq_opt_csb opt;
+ pthread_t th1, th2;
+ void *thret1 = THRET_FAILURE, *thret2 = THRET_FAILURE;
+ struct timespec to;
+ sem_t sem;
+ int err = 0;
+ int ret;
+
+ ret = push_csb_option(ctx, &opt);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = port_register_hwall(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ clear_options(ctx);
+
+ ret = sem_init(&sem, 0, 0);
+ if (ret != 0) {
+ printf("sem_init() failed: %s\n", strerror(ret));
+ return ret;
+ }
+ ctx->sem = &sem;
+
+ ret = pthread_create(&th1, NULL, sync_kloop_worker, ctx);
+ err |= ret;
+ if (ret != 0) {
+ printf("pthread_create(kloop1): %s\n", strerror(ret));
+ }
+
+ ret = pthread_create(&th2, NULL, sync_kloop_worker, ctx);
+ err |= ret;
+ if (ret != 0) {
+ printf("pthread_create(kloop2): %s\n", strerror(ret));
+ }
+
+ /* Wait for one of the two threads to fail to start the kloop, to
+ * avoid a race condition where th1 starts the loop and stops,
+ * and after that th2 starts the loop successfully. */
+ /*
+ * XXX: This doesn't fully close the race. th2 might fail to
+ * start executing since th1 can enter the kernel and hog the
+ * CPU on a single-CPU system until the semaphore timeout
+ * awakens this thread and it calls sync_kloop_stop. Once th1
+ * exits the kernel, th2 can finally run and will then loop
+ * forever in the ioctl handler.
+ */
+ clock_gettime(CLOCK_REALTIME, &to);
+ to.tv_sec += 2;
+ ret = sem_timedwait(&sem, &to);
+ err |= ret;
+ if (ret != 0) {
+ printf("sem_timedwait() failed: %s\n", strerror(errno));
+ }
+
+ err |= sync_kloop_stop(ctx);
+
+ ret = pthread_join(th1, &thret1);
+ err |= ret;
+ if (ret != 0) {
+ printf("pthread_join(kloop1): %s\n", strerror(ret));
+ }
+
+ ret = pthread_join(th2, &thret2);
+ err |= ret;
+ if (ret != 0) {
+ printf("pthread_join(kloop2): %s %d\n", strerror(ret), ret);
+ }
+
+ sem_destroy(&sem);
+ ctx->sem = NULL;
+ if (err) {
+ return err;
+ }
+
+ /* Check that one of the two failed, while the other one succeeded. */
+ return ((thret1 == THRET_SUCCESS && thret2 == THRET_FAILURE) ||
+ (thret1 == THRET_FAILURE && thret2 == THRET_SUCCESS))
+ ? 0
+ : -1;
+}
+#endif
+
+static int
+sync_kloop_eventfds_mismatch(struct TestContext *ctx)
+{
+ struct nmreq_opt_csb opt;
+ int ret;
+
+ ret = push_csb_option(ctx, &opt);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = port_register_hwall_rx(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ clear_options(ctx);
+
+ /* Deceive num_registered_rings() to trigger a failure of
+ * sync_kloop_eventfds(). The latter will think that all the
+ * rings were registered, and allocate the wrong number of
+ * eventfds. */
+ ctx->nr_flags &= ~NR_RX_RINGS_ONLY;
+
+ return (sync_kloop_eventfds(ctx) != 0) ? 0 : -1;
+}
+
+static int
+null_port(struct TestContext *ctx)
+{
+ int ret;
+
+ ctx->nr_mem_id = 1;
+ ctx->nr_mode = NR_REG_NULL;
+ ctx->nr_tx_rings = 10;
+ ctx->nr_rx_rings = 5;
+ ctx->nr_tx_slots = 256;
+ ctx->nr_rx_slots = 100;
+ ret = port_register(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ return 0;
+}
+
+static int
+null_port_all_zero(struct TestContext *ctx)
+{
+ int ret;
+
+ ctx->nr_mem_id = 1;
+ ctx->nr_mode = NR_REG_NULL;
+ ctx->nr_tx_rings = 0;
+ ctx->nr_rx_rings = 0;
+ ctx->nr_tx_slots = 0;
+ ctx->nr_rx_slots = 0;
+ ret = port_register(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ return 0;
+}
+
+static int
+null_port_sync(struct TestContext *ctx)
+{
+ int ret;
+
+ ctx->nr_mem_id = 1;
+ ctx->nr_mode = NR_REG_NULL;
+ ctx->nr_tx_rings = 10;
+ ctx->nr_rx_rings = 5;
+ ctx->nr_tx_slots = 256;
+ ctx->nr_rx_slots = 100;
+ ret = port_register(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = ioctl(ctx->fd, NIOCTXSYNC, 0);
+ if (ret != 0) {
+ return ret;
+ }
+ return 0;
+}
+
+struct nmreq_parse_test {
+ const char *ifname;
+ const char *exp_port;
+ const char *exp_suff;
+ int exp_error;
+ uint32_t exp_mode;
+ uint16_t exp_ringid;
+ uint64_t exp_flags;
+};
+
+static struct nmreq_parse_test nmreq_parse_tests[] = {
+ /* port spec is the input. The expected results are as follows:
+ * - port: what should go into hdr.nr_name
+ * - suff: the trailing part of the input after parsing (NULL means equal to port spec)
+ * - err: the expected return value, interpreted as follows
+ * err > 0 => nmreq_header_parse should fail with the given error
+ * err < 0 => nrmeq_header_parse should succeed, but nmreq_register_decode should
+ * fail with error |err|
+ * err = 0 => should succeed
+ * - mode, ringid flags: what should go into the corresponding nr_* fields in the
+ * nmreq_register struct in case of success
+ */
+
+ /*port spec*/ /*port*/ /*suff*/ /*err*/ /*mode*/ /*ringid*/ /*flags*/
+ { "netmap:eth0", "eth0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:eth0-1", "eth0", "", 0, NR_REG_ONE_NIC, 1, 0 },
+ { "netmap:eth0-", "eth0", "-", -EINVAL,0, 0, 0 },
+ { "netmap:eth0/x", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_EXCLUSIVE },
+ { "netmap:eth0/z", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_ZCOPY_MON },
+ { "netmap:eth0/r", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_MONITOR_RX },
+ { "netmap:eth0/t", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_MONITOR_TX },
+ { "netmap:eth0-2/Tx", "eth0", "", 0, NR_REG_ONE_NIC, 2, NR_TX_RINGS_ONLY|NR_EXCLUSIVE },
+ { "netmap:eth0*", "eth0", "", 0, NR_REG_NIC_SW, 0, 0 },
+ { "netmap:eth0^", "eth0", "", 0, NR_REG_SW, 0, 0 },
+ { "netmap:eth0@2", "eth0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:eth0@2/R", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
+ { "netmap:eth0@netmap:lo/R", "eth0", "@netmap:lo/R", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:eth0/R@xxx", "eth0", "@xxx", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
+ { "netmap:eth0@2/R@2", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
+ { "netmap:eth0@2/R@3", "eth0", "@2/R@3", -EINVAL,0, 0, 0 },
+ { "netmap:eth0@", "eth0", "@", -EINVAL,0, 0, 0 },
+ { "netmap:", "", NULL, EINVAL, 0, 0, 0 },
+ { "netmap:^", "", NULL, EINVAL, 0, 0, 0 },
+ { "netmap:{", "", NULL, EINVAL, 0, 0, 0 },
+ { "eth0", NULL, NULL, EINVAL, 0, 0, 0 },
+ { "vale0:0", "vale0:0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "vale:0", "vale:0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "valeXXX:YYY", "valeXXX:YYY", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "valeXXX:YYY-4", "valeXXX:YYY", "", 0, NR_REG_ONE_NIC, 4, 0 },
+ { "netmapXXX:eth0", NULL, NULL, EINVAL, 0, 0, 0 },
+ { "netmap:14", "14", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:pipe{0", "pipe{0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:pipe{in", "pipe{in", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:pipe{in-7", "pipe{in", "", 0, NR_REG_ONE_NIC, 7, 0 },
+ { "vale0:0{0", "vale0:0{0", "", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "netmap:pipe{1}2", NULL, NULL, EINVAL, 0, 0, 0 },
+ { "vale0:0@opt", "vale0:0", "@opt", 0, NR_REG_ALL_NIC, 0, 0 },
+ { "vale0:0/Tx@opt", "vale0:0", "@opt", 0, NR_REG_ALL_NIC, 0, NR_TX_RINGS_ONLY|NR_EXCLUSIVE },
+ { "vale0:0-3@opt", "vale0:0", "@opt", 0, NR_REG_ONE_NIC, 3, 0 },
+ { "vale0:0@", "vale0:0", "@", -EINVAL,0, 0, 0 },
+ { "", NULL, NULL, EINVAL, 0, 0, 0 },
+ { NULL, NULL, NULL, 0, 0, 0, 0 },
+};
+
+static void
+randomize(void *dst, size_t n)
+{
+ size_t i;
+ char *dst_ = dst;
+
+ for (i = 0; i < n; i++)
+ dst_[i] = (char)random();
+}
+
+static int
+nmreq_hdr_parsing(struct TestContext *ctx,
+ struct nmreq_parse_test *t,
+ struct nmreq_header *hdr)
+{
+ const char *save;
+ struct nmreq_header orig_hdr;
+
+ save = ctx->ifparse = t->ifname;
+ orig_hdr = *hdr;
+
+ printf("nmreq_header: \"%s\"\n", ctx->ifparse);
+ if (nmreq_header_decode(&ctx->ifparse, hdr, ctx->nmctx) < 0) {
+ if (t->exp_error > 0) {
+ if (errno != t->exp_error) {
+ printf("!!! got errno=%d, want %d\n",
+ errno, t->exp_error);
+ return -1;
+ }
+ if (ctx->ifparse != save) {
+ printf("!!! parse error, but first arg changed\n");
+ return -1;
+ }
+ if (memcmp(&orig_hdr, hdr, sizeof(*hdr))) {
+ printf("!!! parse error, but header changed\n");
+ return -1;
+ }
+ return 0;
+ }
+ printf ("!!! nmreq_header_decode was expected to succeed, but it failed with error %d\n", errno);
+ return -1;
+ }
+ if (t->exp_error > 0) {
+ printf("!!! nmreq_header_decode returns 0, but error %d was expected\n", t->exp_error);
+ return -1;
+ }
+ if (strcmp(t->exp_port, hdr->nr_name) != 0) {
+ printf("!!! got '%s', want '%s'\n", hdr->nr_name, t->exp_port);
+ return -1;
+ }
+ if (hdr->nr_reqtype != orig_hdr.nr_reqtype ||
+ hdr->nr_options != orig_hdr.nr_options ||
+ hdr->nr_body != orig_hdr.nr_body) {
+ printf("!!! some fields of the nmreq_header where changed unexpectedly\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+nmreq_reg_parsing(struct TestContext *ctx,
+ struct nmreq_parse_test *t,
+ struct nmreq_register *reg)
+{
+ const char *save;
+ struct nmreq_register orig_reg;
+
+
+ save = ctx->ifparse;
+ orig_reg = *reg;
+
+ printf("nmreq_register: \"%s\"\n", ctx->ifparse);
+ if (nmreq_register_decode(&ctx->ifparse, reg, ctx->nmctx) < 0) {
+ if (t->exp_error < 0) {
+ if (errno != -t->exp_error) {
+ printf("!!! got errno=%d, want %d\n",
+ errno, -t->exp_error);
+ return -1;
+ }
+ if (ctx->ifparse != save) {
+ printf("!!! parse error, but first arg changed\n");
+ return -1;
+ }
+ if (memcmp(&orig_reg, reg, sizeof(*reg))) {
+ printf("!!! parse error, but nmreq_register changed\n");
+ return -1;
+ }
+ return 0;
+ }
+ printf ("!!! parse failed but it should have succeeded\n");
+ return -1;
+ }
+ if (t->exp_error < 0) {
+ printf("!!! nmreq_register_decode returns 0, but error %d was expected\n", -t->exp_error);
+ return -1;
+ }
+ if (reg->nr_mode != t->exp_mode) {
+ printf("!!! got nr_mode '%d', want '%d'\n", reg->nr_mode, t->exp_mode);
+ return -1;
+ }
+ if (reg->nr_ringid != t->exp_ringid) {
+ printf("!!! got nr_ringid '%d', want '%d'\n", reg->nr_ringid, t->exp_ringid);
+ return -1;
+ }
+ if (reg->nr_flags != t->exp_flags) {
+ printf("!!! got nm_flags '%llx', want '%llx\n", (unsigned long long)reg->nr_flags,
+ (unsigned long long)t->exp_flags);
+ return -1;
+ }
+ if (reg->nr_offset != orig_reg.nr_offset ||
+ reg->nr_memsize != orig_reg.nr_memsize ||
+ reg->nr_tx_slots != orig_reg.nr_tx_slots ||
+ reg->nr_rx_slots != orig_reg.nr_rx_slots ||
+ reg->nr_tx_rings != orig_reg.nr_tx_rings ||
+ reg->nr_rx_rings != orig_reg.nr_rx_rings ||
+ reg->nr_extra_bufs != orig_reg.nr_extra_bufs)
+ {
+ printf("!!! some fields of the nmreq_register where changed unexpectedly\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+nmctx_parsing_error(struct nmctx *ctx, const char *msg)
+{
+ (void)ctx;
+ printf(" got message: %s\n", msg);
+}
+
+static int
+nmreq_parsing(struct TestContext *ctx)
+{
+ struct nmreq_parse_test *t;
+ struct nmreq_header hdr;
+ struct nmreq_register reg;
+ struct nmctx test_nmctx, *nmctx;
+ int ret = 0;
+
+ nmctx = nmctx_get();
+ if (nmctx == NULL) {
+ printf("Failed to acquire nmctx: %s", strerror(errno));
+ return -1;
+ }
+ test_nmctx = *nmctx;
+ test_nmctx.error = nmctx_parsing_error;
+ ctx->nmctx = &test_nmctx;
+ for (t = nmreq_parse_tests; t->ifname != NULL; t++) {
+ const char *exp_suff = t->exp_suff != NULL ?
+ t->exp_suff : t->ifname;
+
+ randomize(&hdr, sizeof(hdr));
+ randomize(&reg, sizeof(reg));
+ reg.nr_mem_id = 0;
+ if (nmreq_hdr_parsing(ctx, t, &hdr) < 0) {
+ ret = -1;
+ } else if (t->exp_error <= 0 && nmreq_reg_parsing(ctx, t, &reg) < 0) {
+ ret = -1;
+ }
+ if (strcmp(ctx->ifparse, exp_suff) != 0) {
+ printf("!!! string suffix after parse is '%s', but it should be '%s'\n",
+ ctx->ifparse, exp_suff);
+ ret = -1;
+ }
+ }
+ ctx->nmctx = NULL;
+ return ret;
+}
+
+static int
+binarycomp(struct TestContext *ctx)
+{
+#define ckroff(f, o) do {\
+ if (offsetof(struct netmap_ring, f) != (o)) {\
+ printf("offset of netmap_ring.%s is %zd, but it should be %d",\
+ #f, offsetof(struct netmap_ring, f), (o));\
+ return -1;\
+ }\
+} while (0)
+
+ (void)ctx;
+
+ ckroff(buf_ofs, 0);
+ ckroff(num_slots, 8);
+ ckroff(nr_buf_size, 12);
+ ckroff(ringid, 16);
+ ckroff(dir, 18);
+ ckroff(head, 20);
+ ckroff(cur, 24);
+ ckroff(tail, 28);
+ ckroff(flags, 32);
+ ckroff(ts, 40);
+ ckroff(offset_mask, 56);
+ ckroff(buf_align, 64);
+ ckroff(sem, 128);
+ ckroff(slot, 256);
+
+ return 0;
+}
+
+static void
+usage(const char *prog)
+{
+ printf("%s -i IFNAME\n"
+ "[-j TEST_NUM1[-[TEST_NUM2]] | -[TEST_NUM_2]]\n"
+ "[-l (list test cases)]\n",
+ prog);
+}
+
+struct mytest {
+ testfunc_t test;
+ const char *name;
+};
+
+#define decltest(f) \
+ { \
+ .test = f, .name = #f \
+ }
+
+static struct mytest tests[] = {
+ decltest(port_info_get),
+ decltest(port_register_hwall_host),
+ decltest(port_register_hwall),
+ decltest(port_register_hostall),
+ decltest(port_register_single_hw_pair),
+ decltest(port_register_single_host_pair),
+ decltest(port_register_hostall_many),
+ decltest(vale_attach_detach),
+ decltest(vale_attach_detach_host_rings),
+ decltest(vale_ephemeral_port_hdr_manipulation),
+ decltest(vale_persistent_port),
+ decltest(pools_info_get_and_register),
+ decltest(pools_info_get_empty_ifname),
+ decltest(pipe_master),
+ decltest(pipe_slave),
+ decltest(pipe_port_info_get),
+ decltest(pipe_pools_info_get),
+ decltest(vale_polling_enable_disable),
+ decltest(unsupported_option),
+ decltest(infinite_options),
+ decltest(infinite_options2),
+#ifdef CONFIG_NETMAP_EXTMEM
+ decltest(extmem_option),
+ decltest(bad_extmem_option),
+ decltest(duplicate_extmem_options),
+#endif /* CONFIG_NETMAP_EXTMEM */
+ decltest(csb_mode),
+ decltest(csb_mode_invalid_memory),
+ decltest(sync_kloop),
+ decltest(sync_kloop_eventfds_all),
+ decltest(sync_kloop_eventfds_all_tx),
+ decltest(sync_kloop_eventfds_all_direct),
+ decltest(sync_kloop_eventfds_all_direct_tx),
+ decltest(sync_kloop_eventfds_all_direct_rx),
+ decltest(sync_kloop_nocsb),
+ decltest(sync_kloop_csb_enable),
+#if 0
+ decltest(sync_kloop_conflict),
+#endif
+ decltest(sync_kloop_eventfds_mismatch),
+ decltest(null_port),
+ decltest(null_port_all_zero),
+ decltest(null_port_sync),
+ decltest(legacy_regif_default),
+ decltest(legacy_regif_all_nic),
+ decltest(legacy_regif_12),
+ decltest(legacy_regif_sw),
+ decltest(legacy_regif_future),
+ decltest(legacy_regif_extra_bufs),
+ decltest(legacy_regif_extra_bufs_pipe),
+ decltest(legacy_regif_extra_bufs_pipe_vale),
+ decltest(nmreq_parsing),
+ decltest(binarycomp),
+};
+
+static void
+context_cleanup(struct TestContext *ctx)
+{
+ if (ctx->csb) {
+ free(ctx->csb);
+ ctx->csb = NULL;
+ }
+
+ close(ctx->fd);
+ ctx->fd = -1;
+}
+
+static int
+parse_interval(const char *arg, int *j, int *k)
+{
+ const char *scan = arg;
+ char *rest;
+
+ *j = 0;
+ *k = -1;
+ if (*scan == '-') {
+ scan++;
+ goto get_k;
+ }
+ if (!isdigit(*scan))
+ goto err;
+ *k = strtol(scan, &rest, 10);
+ *j = *k - 1;
+ scan = rest;
+ if (*scan == '-') {
+ *k = -1;
+ scan++;
+ }
+get_k:
+ if (*scan == '\0')
+ return 0;
+ if (!isdigit(*scan))
+ goto err;
+ *k = strtol(scan, &rest, 10);
+ scan = rest;
+ if (!(*scan == '\0'))
+ goto err;
+
+ return 0;
+
+err:
+ fprintf(stderr, "syntax error in '%s', must be num[-[num]] or -[num]\n", arg);
+ return -1;
+}
+
+#define ARGV_APPEND(_av, _ac, _x)\
+ do {\
+ assert((int)(_ac) < (int)(sizeof(_av)/sizeof((_av)[0])));\
+ (_av)[(_ac)++] = _x;\
+ } while (0)
+
+static void
+tap_cleanup(int signo)
+{
+ const char *av[8];
+ int ac = 0;
+
+ (void)signo;
+#ifdef __FreeBSD__
+ ARGV_APPEND(av, ac, "ifconfig");
+ ARGV_APPEND(av, ac, ctx_.ifname);
+ ARGV_APPEND(av, ac, "destroy");
+#else
+ ARGV_APPEND(av, ac, "ip");
+ ARGV_APPEND(av, ac, "link");
+ ARGV_APPEND(av, ac, "del");
+ ARGV_APPEND(av, ac, ctx_.ifname);
+#endif
+ ARGV_APPEND(av, ac, NULL);
+ if (exec_command(ac, av)) {
+ printf("Failed to destroy tap interface\n");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int create_tap = 1;
+ int num_tests;
+ int ret = 0;
+ int j = 0;
+ int k = -1;
+ int list = 0;
+ int opt;
+ int i;
+
+ memset(&ctx_, 0, sizeof(ctx_));
+
+ {
+ struct timespec t;
+ int idx;
+
+ clock_gettime(CLOCK_REALTIME, &t);
+ srand((unsigned int)t.tv_nsec);
+ idx = rand() % 8000 + 100;
+ snprintf(ctx_.ifname, sizeof(ctx_.ifname), "tap%d", idx);
+ idx = rand() % 800 + 100;
+ snprintf(ctx_.bdgname, sizeof(ctx_.bdgname), "vale%d", idx);
+ }
+
+ while ((opt = getopt(argc, argv, "hi:j:l")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(argv[0]);
+ return 0;
+
+ case 'i':
+ strncpy(ctx_.ifname, optarg, sizeof(ctx_.ifname) - 1);
+ create_tap = 0;
+ break;
+
+ case 'j':
+ if (parse_interval(optarg, &j, &k) < 0) {
+ usage(argv[0]);
+ return -1;
+ }
+ break;
+
+ case 'l':
+ list = 1;
+ create_tap = 0;
+ break;
+
+ default:
+ printf(" Unrecognized option %c\n", opt);
+ usage(argv[0]);
+ return -1;
+ }
+ }
+
+ num_tests = sizeof(tests) / sizeof(tests[0]);
+
+ if (j < 0 || j >= num_tests || k > num_tests) {
+ fprintf(stderr, "Test interval %d-%d out of range (%d-%d)\n",
+ j + 1, k, 1, num_tests + 1);
+ return -1;
+ }
+
+ if (k < 0)
+ k = num_tests;
+
+ if (list) {
+ printf("Available tests:\n");
+ for (i = 0; i < num_tests; i++) {
+ printf("#%03d: %s\n", i + 1, tests[i].name);
+ }
+ return 0;
+ }
+
+ if (create_tap) {
+ struct sigaction sa;
+ const char *av[8];
+ int ac = 0;
+#ifdef __FreeBSD__
+ ARGV_APPEND(av, ac, "ifconfig");
+ ARGV_APPEND(av, ac, ctx_.ifname);
+ ARGV_APPEND(av, ac, "create");
+ ARGV_APPEND(av, ac, "up");
+#else
+ ARGV_APPEND(av, ac, "ip");
+ ARGV_APPEND(av, ac, "tuntap");
+ ARGV_APPEND(av, ac, "add");
+ ARGV_APPEND(av, ac, "mode");
+ ARGV_APPEND(av, ac, "tap");
+ ARGV_APPEND(av, ac, "name");
+ ARGV_APPEND(av, ac, ctx_.ifname);
+#endif
+ ARGV_APPEND(av, ac, NULL);
+ if (exec_command(ac, av)) {
+ printf("Failed to create tap interface\n");
+ return -1;
+ }
+
+ sa.sa_handler = tap_cleanup;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ ret = sigaction(SIGINT, &sa, NULL);
+ if (ret) {
+ perror("sigaction(SIGINT)");
+ goto out;
+ }
+ ret = sigaction(SIGTERM, &sa, NULL);
+ if (ret) {
+ perror("sigaction(SIGTERM)");
+ goto out;
+ }
+ }
+
+ for (i = j; i < k; i++) {
+ struct TestContext ctxcopy;
+ int fd;
+ printf("==> Start of Test #%d [%s]\n", i + 1, tests[i].name);
+ fd = open("/dev/netmap", O_RDWR);
+ if (fd < 0) {
+ perror("open(/dev/netmap)");
+ ret = fd;
+ goto out;
+ }
+ memcpy(&ctxcopy, &ctx_, sizeof(ctxcopy));
+ ctxcopy.fd = fd;
+ memcpy(ctxcopy.ifname_ext, ctxcopy.ifname,
+ sizeof(ctxcopy.ifname));
+ ret = tests[i].test(&ctxcopy);
+ if (ret != 0) {
+ printf("Test #%d [%s] failed\n", i + 1, tests[i].name);
+ goto out;
+ }
+ printf("==> Test #%d [%s] successful\n", i + 1, tests[i].name);
+ context_cleanup(&ctxcopy);
+ }
+out:
+ tap_cleanup(0);
+
+ return ret;
+}