Path: blob/master/tools/testing/selftests/drivers/net/hw/ncdevmem.c
26295 views
// SPDX-License-Identifier: GPL-2.01/*2* tcpdevmem netcat. Works similarly to netcat but does device memory TCP3* instead of regular TCP. Uses udmabuf to mock a dmabuf provider.4*5* Usage:6*7* On server:8* ncdevmem -s <server IP> [-c <client IP>] -f eth1 -l -p 52019*10* On client:11* echo -n "hello\nworld" | \12* ncdevmem -s <server IP> [-c <client IP>] -p 5201 -f eth113*14* Note this is compatible with regular netcat. i.e. the sender or receiver can15* be replaced with regular netcat to test the RX or TX path in isolation.16*17* Test data validation (devmem TCP on RX only):18*19* On server:20* ncdevmem -s <server IP> [-c <client IP>] -f eth1 -l -p 5201 -v 721*22* On client:23* yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06) | \24* head -c 1G | \25* nc <server IP> 5201 -p 520126*27* Test data validation (devmem TCP on RX and TX, validation happens on RX):28*29* On server:30* ncdevmem -s <server IP> [-c <client IP>] -l -p 5201 -v 8 -f eth131*32* On client:33* yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06\\x07) | \34* head -c 1M | \35* ncdevmem -s <server IP> [-c <client IP>] -p 5201 -f eth136*/37#define _GNU_SOURCE38#define __EXPORTED_HEADERS__3940#include <linux/uio.h>41#include <stdio.h>42#include <stdlib.h>43#include <unistd.h>44#include <stdbool.h>45#include <string.h>46#include <errno.h>47#define __iovec_defined48#include <fcntl.h>49#include <malloc.h>50#include <error.h>51#include <poll.h>5253#include <arpa/inet.h>54#include <sys/socket.h>55#include <sys/mman.h>56#include <sys/ioctl.h>57#include <sys/syscall.h>58#include <sys/time.h>5960#include <linux/memfd.h>61#include <linux/dma-buf.h>62#include <linux/errqueue.h>63#include <linux/udmabuf.h>64#include <linux/types.h>65#include <linux/netlink.h>66#include <linux/genetlink.h>67#include <linux/netdev.h>68#include <linux/ethtool_netlink.h>69#include <time.h>70#include <net/if.h>7172#include "netdev-user.h"73#include "ethtool-user.h"74#include <ynl.h>7576#define PAGE_SHIFT 1277#define TEST_PREFIX "ncdevmem"78#define NUM_PAGES 160007980#ifndef MSG_SOCK_DEVMEM81#define MSG_SOCK_DEVMEM 0x200000082#endif8384#define MAX_IOV 10248586static size_t max_chunk;87static char *server_ip;88static char *client_ip;89static char *port;90static size_t do_validation;91static int start_queue = -1;92static int num_queues = -1;93static char *ifname;94static unsigned int ifindex;95static unsigned int dmabuf_id;96static uint32_t tx_dmabuf_id;97static int waittime_ms = 500;9899struct memory_buffer {100int fd;101size_t size;102103int devfd;104int memfd;105char *buf_mem;106};107108struct memory_provider {109struct memory_buffer *(*alloc)(size_t size);110void (*free)(struct memory_buffer *ctx);111void (*memcpy_to_device)(struct memory_buffer *dst, size_t off,112void *src, int n);113void (*memcpy_from_device)(void *dst, struct memory_buffer *src,114size_t off, int n);115};116117static struct memory_buffer *udmabuf_alloc(size_t size)118{119struct udmabuf_create create;120struct memory_buffer *ctx;121int ret;122123ctx = malloc(sizeof(*ctx));124if (!ctx)125error(1, ENOMEM, "malloc failed");126127ctx->size = size;128129ctx->devfd = open("/dev/udmabuf", O_RDWR);130if (ctx->devfd < 0)131error(1, errno,132"%s: [skip,no-udmabuf: Unable to access DMA buffer device file]\n",133TEST_PREFIX);134135ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING);136if (ctx->memfd < 0)137error(1, errno, "%s: [skip,no-memfd]\n", TEST_PREFIX);138139ret = fcntl(ctx->memfd, F_ADD_SEALS, F_SEAL_SHRINK);140if (ret < 0)141error(1, errno, "%s: [skip,fcntl-add-seals]\n", TEST_PREFIX);142143ret = ftruncate(ctx->memfd, size);144if (ret == -1)145error(1, errno, "%s: [FAIL,memfd-truncate]\n", TEST_PREFIX);146147memset(&create, 0, sizeof(create));148149create.memfd = ctx->memfd;150create.offset = 0;151create.size = size;152ctx->fd = ioctl(ctx->devfd, UDMABUF_CREATE, &create);153if (ctx->fd < 0)154error(1, errno, "%s: [FAIL, create udmabuf]\n", TEST_PREFIX);155156ctx->buf_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,157ctx->fd, 0);158if (ctx->buf_mem == MAP_FAILED)159error(1, errno, "%s: [FAIL, map udmabuf]\n", TEST_PREFIX);160161return ctx;162}163164static void udmabuf_free(struct memory_buffer *ctx)165{166munmap(ctx->buf_mem, ctx->size);167close(ctx->fd);168close(ctx->memfd);169close(ctx->devfd);170free(ctx);171}172173static void udmabuf_memcpy_to_device(struct memory_buffer *dst, size_t off,174void *src, int n)175{176struct dma_buf_sync sync = {};177178sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;179ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);180181memcpy(dst->buf_mem + off, src, n);182183sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;184ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);185}186187static void udmabuf_memcpy_from_device(void *dst, struct memory_buffer *src,188size_t off, int n)189{190struct dma_buf_sync sync = {};191192sync.flags = DMA_BUF_SYNC_START;193ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);194195memcpy(dst, src->buf_mem + off, n);196197sync.flags = DMA_BUF_SYNC_END;198ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);199}200201static struct memory_provider udmabuf_memory_provider = {202.alloc = udmabuf_alloc,203.free = udmabuf_free,204.memcpy_to_device = udmabuf_memcpy_to_device,205.memcpy_from_device = udmabuf_memcpy_from_device,206};207208static struct memory_provider *provider = &udmabuf_memory_provider;209210static void print_nonzero_bytes(void *ptr, size_t size)211{212unsigned char *p = ptr;213unsigned int i;214215for (i = 0; i < size; i++)216putchar(p[i]);217}218219void validate_buffer(void *line, size_t size)220{221static unsigned char seed = 1;222unsigned char *ptr = line;223unsigned char expected;224static int errors;225size_t i;226227for (i = 0; i < size; i++) {228expected = seed ? seed : '\n';229if (ptr[i] != expected) {230fprintf(stderr,231"Failed validation: expected=%u, actual=%u, index=%lu\n",232expected, ptr[i], i);233errors++;234if (errors > 20)235error(1, 0, "validation failed.");236}237seed++;238if (seed == do_validation)239seed = 0;240}241242fprintf(stdout, "Validated buffer\n");243}244245static int rxq_num(int ifindex)246{247struct ethtool_channels_get_req *req;248struct ethtool_channels_get_rsp *rsp;249struct ynl_error yerr;250struct ynl_sock *ys;251int num = -1;252253ys = ynl_sock_create(&ynl_ethtool_family, &yerr);254if (!ys) {255fprintf(stderr, "YNL: %s\n", yerr.msg);256return -1;257}258259req = ethtool_channels_get_req_alloc();260ethtool_channels_get_req_set_header_dev_index(req, ifindex);261rsp = ethtool_channels_get(ys, req);262if (rsp)263num = rsp->rx_count + rsp->combined_count;264ethtool_channels_get_req_free(req);265ethtool_channels_get_rsp_free(rsp);266267ynl_sock_destroy(ys);268269return num;270}271272#define run_command(cmd, ...) \273({ \274char command[256]; \275memset(command, 0, sizeof(command)); \276snprintf(command, sizeof(command), cmd, ##__VA_ARGS__); \277fprintf(stderr, "Running: %s\n", command); \278system(command); \279})280281static int reset_flow_steering(void)282{283/* Depending on the NIC, toggling ntuple off and on might not284* be allowed. Additionally, attempting to delete existing filters285* will fail if no filters are present. Therefore, do not enforce286* the exit status.287*/288289run_command("sudo ethtool -K %s ntuple off >&2", ifname);290run_command("sudo ethtool -K %s ntuple on >&2", ifname);291run_command(292"sudo ethtool -n %s | grep 'Filter:' | awk '{print $2}' | xargs -n1 ethtool -N %s delete >&2",293ifname, ifname);294return 0;295}296297static const char *tcp_data_split_str(int val)298{299switch (val) {300case 0:301return "off";302case 1:303return "auto";304case 2:305return "on";306default:307return "?";308}309}310311static int configure_headersplit(bool on)312{313struct ethtool_rings_get_req *get_req;314struct ethtool_rings_get_rsp *get_rsp;315struct ethtool_rings_set_req *req;316struct ynl_error yerr;317struct ynl_sock *ys;318int ret;319320ys = ynl_sock_create(&ynl_ethtool_family, &yerr);321if (!ys) {322fprintf(stderr, "YNL: %s\n", yerr.msg);323return -1;324}325326req = ethtool_rings_set_req_alloc();327ethtool_rings_set_req_set_header_dev_index(req, ifindex);328/* 0 - off, 1 - auto, 2 - on */329ethtool_rings_set_req_set_tcp_data_split(req, on ? 2 : 0);330ret = ethtool_rings_set(ys, req);331if (ret < 0)332fprintf(stderr, "YNL failed: %s\n", ys->err.msg);333ethtool_rings_set_req_free(req);334335if (ret == 0) {336get_req = ethtool_rings_get_req_alloc();337ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);338get_rsp = ethtool_rings_get(ys, get_req);339ethtool_rings_get_req_free(get_req);340if (get_rsp)341fprintf(stderr, "TCP header split: %s\n",342tcp_data_split_str(get_rsp->tcp_data_split));343ethtool_rings_get_rsp_free(get_rsp);344}345346ynl_sock_destroy(ys);347348return ret;349}350351static int configure_rss(void)352{353return run_command("sudo ethtool -X %s equal %d >&2", ifname, start_queue);354}355356static int configure_channels(unsigned int rx, unsigned int tx)357{358return run_command("sudo ethtool -L %s rx %u tx %u", ifname, rx, tx);359}360361static int configure_flow_steering(struct sockaddr_in6 *server_sin)362{363const char *type = "tcp6";364const char *server_addr;365char buf[40];366367inet_ntop(AF_INET6, &server_sin->sin6_addr, buf, sizeof(buf));368server_addr = buf;369370if (IN6_IS_ADDR_V4MAPPED(&server_sin->sin6_addr)) {371type = "tcp4";372server_addr = strrchr(server_addr, ':') + 1;373}374375/* Try configure 5-tuple */376if (run_command("sudo ethtool -N %s flow-type %s %s %s dst-ip %s %s %s dst-port %s queue %d >&2",377ifname,378type,379client_ip ? "src-ip" : "",380client_ip ?: "",381server_addr,382client_ip ? "src-port" : "",383client_ip ? port : "",384port, start_queue))385/* If that fails, try configure 3-tuple */386if (run_command("sudo ethtool -N %s flow-type %s dst-ip %s dst-port %s queue %d >&2",387ifname,388type,389server_addr,390port, start_queue))391/* If that fails, return error */392return -1;393394return 0;395}396397static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd,398struct netdev_queue_id *queues,399unsigned int n_queue_index, struct ynl_sock **ys)400{401struct netdev_bind_rx_req *req = NULL;402struct netdev_bind_rx_rsp *rsp = NULL;403struct ynl_error yerr;404405*ys = ynl_sock_create(&ynl_netdev_family, &yerr);406if (!*ys) {407fprintf(stderr, "YNL: %s\n", yerr.msg);408return -1;409}410411req = netdev_bind_rx_req_alloc();412netdev_bind_rx_req_set_ifindex(req, ifindex);413netdev_bind_rx_req_set_fd(req, dmabuf_fd);414__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);415416rsp = netdev_bind_rx(*ys, req);417if (!rsp) {418perror("netdev_bind_rx");419goto err_close;420}421422if (!rsp->_present.id) {423perror("id not present");424goto err_close;425}426427fprintf(stderr, "got dmabuf id=%d\n", rsp->id);428dmabuf_id = rsp->id;429430netdev_bind_rx_req_free(req);431netdev_bind_rx_rsp_free(rsp);432433return 0;434435err_close:436fprintf(stderr, "YNL failed: %s\n", (*ys)->err.msg);437netdev_bind_rx_req_free(req);438ynl_sock_destroy(*ys);439return -1;440}441442static int bind_tx_queue(unsigned int ifindex, unsigned int dmabuf_fd,443struct ynl_sock **ys)444{445struct netdev_bind_tx_req *req = NULL;446struct netdev_bind_tx_rsp *rsp = NULL;447struct ynl_error yerr;448449*ys = ynl_sock_create(&ynl_netdev_family, &yerr);450if (!*ys) {451fprintf(stderr, "YNL: %s\n", yerr.msg);452return -1;453}454455req = netdev_bind_tx_req_alloc();456netdev_bind_tx_req_set_ifindex(req, ifindex);457netdev_bind_tx_req_set_fd(req, dmabuf_fd);458459rsp = netdev_bind_tx(*ys, req);460if (!rsp) {461perror("netdev_bind_tx");462goto err_close;463}464465if (!rsp->_present.id) {466perror("id not present");467goto err_close;468}469470fprintf(stderr, "got tx dmabuf id=%d\n", rsp->id);471tx_dmabuf_id = rsp->id;472473netdev_bind_tx_req_free(req);474netdev_bind_tx_rsp_free(rsp);475476return 0;477478err_close:479fprintf(stderr, "YNL failed: %s\n", (*ys)->err.msg);480netdev_bind_tx_req_free(req);481ynl_sock_destroy(*ys);482return -1;483}484485static void enable_reuseaddr(int fd)486{487int opt = 1;488int ret;489490ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));491if (ret)492error(1, errno, "%s: [FAIL, SO_REUSEPORT]\n", TEST_PREFIX);493494ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));495if (ret)496error(1, errno, "%s: [FAIL, SO_REUSEADDR]\n", TEST_PREFIX);497}498499static int parse_address(const char *str, int port, struct sockaddr_in6 *sin6)500{501int ret;502503sin6->sin6_family = AF_INET6;504sin6->sin6_port = htons(port);505506ret = inet_pton(sin6->sin6_family, str, &sin6->sin6_addr);507if (ret != 1) {508/* fallback to plain IPv4 */509ret = inet_pton(AF_INET, str, &sin6->sin6_addr.s6_addr32[3]);510if (ret != 1)511return -1;512513/* add ::ffff prefix */514sin6->sin6_addr.s6_addr32[0] = 0;515sin6->sin6_addr.s6_addr32[1] = 0;516sin6->sin6_addr.s6_addr16[4] = 0;517sin6->sin6_addr.s6_addr16[5] = 0xffff;518}519520return 0;521}522523static struct netdev_queue_id *create_queues(void)524{525struct netdev_queue_id *queues;526size_t i = 0;527528queues = netdev_queue_id_alloc(num_queues);529for (i = 0; i < num_queues; i++) {530netdev_queue_id_set_type(&queues[i], NETDEV_QUEUE_TYPE_RX);531netdev_queue_id_set_id(&queues[i], start_queue + i);532}533534return queues;535}536537static int do_server(struct memory_buffer *mem)538{539char ctrl_data[sizeof(int) * 20000];540size_t non_page_aligned_frags = 0;541struct sockaddr_in6 client_addr;542struct sockaddr_in6 server_sin;543size_t page_aligned_frags = 0;544size_t total_received = 0;545socklen_t client_addr_len;546bool is_devmem = false;547char *tmp_mem = NULL;548struct ynl_sock *ys;549char iobuf[819200];550char buffer[256];551int socket_fd;552int client_fd;553int ret;554555ret = parse_address(server_ip, atoi(port), &server_sin);556if (ret < 0)557error(1, 0, "parse server address");558559if (reset_flow_steering())560error(1, 0, "Failed to reset flow steering\n");561562if (configure_headersplit(1))563error(1, 0, "Failed to enable TCP header split\n");564565/* Configure RSS to divert all traffic from our devmem queues */566if (configure_rss())567error(1, 0, "Failed to configure rss\n");568569/* Flow steer our devmem flows to start_queue */570if (configure_flow_steering(&server_sin))571error(1, 0, "Failed to configure flow steering\n");572573sleep(1);574575if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys))576error(1, 0, "Failed to bind\n");577578tmp_mem = malloc(mem->size);579if (!tmp_mem)580error(1, ENOMEM, "malloc failed");581582socket_fd = socket(AF_INET6, SOCK_STREAM, 0);583if (socket_fd < 0)584error(1, errno, "%s: [FAIL, create socket]\n", TEST_PREFIX);585586enable_reuseaddr(socket_fd);587588fprintf(stderr, "binding to address %s:%d\n", server_ip,589ntohs(server_sin.sin6_port));590591ret = bind(socket_fd, &server_sin, sizeof(server_sin));592if (ret)593error(1, errno, "%s: [FAIL, bind]\n", TEST_PREFIX);594595ret = listen(socket_fd, 1);596if (ret)597error(1, errno, "%s: [FAIL, listen]\n", TEST_PREFIX);598599client_addr_len = sizeof(client_addr);600601inet_ntop(AF_INET6, &server_sin.sin6_addr, buffer,602sizeof(buffer));603fprintf(stderr, "Waiting or connection on %s:%d\n", buffer,604ntohs(server_sin.sin6_port));605client_fd = accept(socket_fd, &client_addr, &client_addr_len);606607inet_ntop(AF_INET6, &client_addr.sin6_addr, buffer,608sizeof(buffer));609fprintf(stderr, "Got connection from %s:%d\n", buffer,610ntohs(client_addr.sin6_port));611612while (1) {613struct iovec iov = { .iov_base = iobuf,614.iov_len = sizeof(iobuf) };615struct dmabuf_cmsg *dmabuf_cmsg = NULL;616struct cmsghdr *cm = NULL;617struct msghdr msg = { 0 };618struct dmabuf_token token;619ssize_t ret;620621is_devmem = false;622623msg.msg_iov = &iov;624msg.msg_iovlen = 1;625msg.msg_control = ctrl_data;626msg.msg_controllen = sizeof(ctrl_data);627ret = recvmsg(client_fd, &msg, MSG_SOCK_DEVMEM);628fprintf(stderr, "recvmsg ret=%ld\n", ret);629if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))630continue;631if (ret < 0) {632perror("recvmsg");633continue;634}635if (ret == 0) {636fprintf(stderr, "client exited\n");637goto cleanup;638}639640for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {641if (cm->cmsg_level != SOL_SOCKET ||642(cm->cmsg_type != SCM_DEVMEM_DMABUF &&643cm->cmsg_type != SCM_DEVMEM_LINEAR)) {644fprintf(stderr, "skipping non-devmem cmsg\n");645continue;646}647648dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);649is_devmem = true;650651if (cm->cmsg_type == SCM_DEVMEM_LINEAR) {652/* TODO: process data copied from skb's linear653* buffer.654*/655fprintf(stderr,656"SCM_DEVMEM_LINEAR. dmabuf_cmsg->frag_size=%u\n",657dmabuf_cmsg->frag_size);658659continue;660}661662token.token_start = dmabuf_cmsg->frag_token;663token.token_count = 1;664665total_received += dmabuf_cmsg->frag_size;666fprintf(stderr,667"received frag_page=%llu, in_page_offset=%llu, frag_offset=%llu, frag_size=%u, token=%u, total_received=%lu, dmabuf_id=%u\n",668dmabuf_cmsg->frag_offset >> PAGE_SHIFT,669dmabuf_cmsg->frag_offset % getpagesize(),670dmabuf_cmsg->frag_offset,671dmabuf_cmsg->frag_size, dmabuf_cmsg->frag_token,672total_received, dmabuf_cmsg->dmabuf_id);673674if (dmabuf_cmsg->dmabuf_id != dmabuf_id)675error(1, 0,676"received on wrong dmabuf_id: flow steering error\n");677678if (dmabuf_cmsg->frag_size % getpagesize())679non_page_aligned_frags++;680else681page_aligned_frags++;682683provider->memcpy_from_device(tmp_mem, mem,684dmabuf_cmsg->frag_offset,685dmabuf_cmsg->frag_size);686687if (do_validation)688validate_buffer(tmp_mem,689dmabuf_cmsg->frag_size);690else691print_nonzero_bytes(tmp_mem,692dmabuf_cmsg->frag_size);693694ret = setsockopt(client_fd, SOL_SOCKET,695SO_DEVMEM_DONTNEED, &token,696sizeof(token));697if (ret != 1)698error(1, 0,699"SO_DEVMEM_DONTNEED not enough tokens");700}701if (!is_devmem)702error(1, 0, "flow steering error\n");703704fprintf(stderr, "total_received=%lu\n", total_received);705}706707fprintf(stderr, "%s: ok\n", TEST_PREFIX);708709fprintf(stderr, "page_aligned_frags=%lu, non_page_aligned_frags=%lu\n",710page_aligned_frags, non_page_aligned_frags);711712cleanup:713714free(tmp_mem);715close(client_fd);716close(socket_fd);717ynl_sock_destroy(ys);718719return 0;720}721722void run_devmem_tests(void)723{724struct memory_buffer *mem;725struct ynl_sock *ys;726727mem = provider->alloc(getpagesize() * NUM_PAGES);728729/* Configure RSS to divert all traffic from our devmem queues */730if (configure_rss())731error(1, 0, "rss error\n");732733if (configure_headersplit(1))734error(1, 0, "Failed to configure header split\n");735736if (!bind_rx_queue(ifindex, mem->fd,737calloc(num_queues, sizeof(struct netdev_queue_id)),738num_queues, &ys))739error(1, 0, "Binding empty queues array should have failed\n");740741if (configure_headersplit(0))742error(1, 0, "Failed to configure header split\n");743744if (!bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys))745error(1, 0, "Configure dmabuf with header split off should have failed\n");746747if (configure_headersplit(1))748error(1, 0, "Failed to configure header split\n");749750if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys))751error(1, 0, "Failed to bind\n");752753/* Deactivating a bound queue should not be legal */754if (!configure_channels(num_queues, num_queues - 1))755error(1, 0, "Deactivating a bound queue should be illegal.\n");756757/* Closing the netlink socket does an implicit unbind */758ynl_sock_destroy(ys);759760provider->free(mem);761}762763static uint64_t gettimeofday_ms(void)764{765struct timeval tv;766767gettimeofday(&tv, NULL);768return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000ULL);769}770771static int do_poll(int fd)772{773struct pollfd pfd;774int ret;775776pfd.revents = 0;777pfd.fd = fd;778779ret = poll(&pfd, 1, waittime_ms);780if (ret == -1)781error(1, errno, "poll");782783return ret && (pfd.revents & POLLERR);784}785786static void wait_compl(int fd)787{788int64_t tstop = gettimeofday_ms() + waittime_ms;789char control[CMSG_SPACE(100)] = {};790struct sock_extended_err *serr;791struct msghdr msg = {};792struct cmsghdr *cm;793__u32 hi, lo;794int ret;795796msg.msg_control = control;797msg.msg_controllen = sizeof(control);798799while (gettimeofday_ms() < tstop) {800if (!do_poll(fd))801continue;802803ret = recvmsg(fd, &msg, MSG_ERRQUEUE);804if (ret < 0) {805if (errno == EAGAIN)806continue;807error(1, errno, "recvmsg(MSG_ERRQUEUE)");808return;809}810if (msg.msg_flags & MSG_CTRUNC)811error(1, 0, "MSG_CTRUNC\n");812813for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {814if (cm->cmsg_level != SOL_IP &&815cm->cmsg_level != SOL_IPV6)816continue;817if (cm->cmsg_level == SOL_IP &&818cm->cmsg_type != IP_RECVERR)819continue;820if (cm->cmsg_level == SOL_IPV6 &&821cm->cmsg_type != IPV6_RECVERR)822continue;823824serr = (void *)CMSG_DATA(cm);825if (serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY)826error(1, 0, "wrong origin %u", serr->ee_origin);827if (serr->ee_errno != 0)828error(1, 0, "wrong errno %d", serr->ee_errno);829830hi = serr->ee_data;831lo = serr->ee_info;832833fprintf(stderr, "tx complete [%d,%d]\n", lo, hi);834return;835}836}837838error(1, 0, "did not receive tx completion");839}840841static int do_client(struct memory_buffer *mem)842{843char ctrl_data[CMSG_SPACE(sizeof(__u32))];844struct sockaddr_in6 server_sin;845struct sockaddr_in6 client_sin;846struct ynl_sock *ys = NULL;847struct iovec iov[MAX_IOV];848struct msghdr msg = {};849ssize_t line_size = 0;850struct cmsghdr *cmsg;851char *line = NULL;852size_t len = 0;853int socket_fd;854__u32 ddmabuf;855int opt = 1;856int ret;857858ret = parse_address(server_ip, atoi(port), &server_sin);859if (ret < 0)860error(1, 0, "parse server address");861862socket_fd = socket(AF_INET6, SOCK_STREAM, 0);863if (socket_fd < 0)864error(1, socket_fd, "create socket");865866enable_reuseaddr(socket_fd);867868ret = setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,869strlen(ifname) + 1);870if (ret)871error(1, errno, "bindtodevice");872873if (bind_tx_queue(ifindex, mem->fd, &ys))874error(1, 0, "Failed to bind\n");875876if (client_ip) {877ret = parse_address(client_ip, atoi(port), &client_sin);878if (ret < 0)879error(1, 0, "parse client address");880881ret = bind(socket_fd, &client_sin, sizeof(client_sin));882if (ret)883error(1, errno, "bind");884}885886ret = setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &opt, sizeof(opt));887if (ret)888error(1, errno, "set sock opt");889890fprintf(stderr, "Connect to %s %d (via %s)\n", server_ip,891ntohs(server_sin.sin6_port), ifname);892893ret = connect(socket_fd, &server_sin, sizeof(server_sin));894if (ret)895error(1, errno, "connect");896897while (1) {898free(line);899line = NULL;900line_size = getline(&line, &len, stdin);901902if (line_size < 0)903break;904905if (max_chunk) {906msg.msg_iovlen =907(line_size + max_chunk - 1) / max_chunk;908if (msg.msg_iovlen > MAX_IOV)909error(1, 0,910"can't partition %zd bytes into maximum of %d chunks",911line_size, MAX_IOV);912913for (int i = 0; i < msg.msg_iovlen; i++) {914iov[i].iov_base = (void *)(i * max_chunk);915iov[i].iov_len = max_chunk;916}917918iov[msg.msg_iovlen - 1].iov_len =919line_size - (msg.msg_iovlen - 1) * max_chunk;920} else {921iov[0].iov_base = 0;922iov[0].iov_len = line_size;923msg.msg_iovlen = 1;924}925926msg.msg_iov = iov;927provider->memcpy_to_device(mem, 0, line, line_size);928929msg.msg_control = ctrl_data;930msg.msg_controllen = sizeof(ctrl_data);931932cmsg = CMSG_FIRSTHDR(&msg);933cmsg->cmsg_level = SOL_SOCKET;934cmsg->cmsg_type = SCM_DEVMEM_DMABUF;935cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));936937ddmabuf = tx_dmabuf_id;938939*((__u32 *)CMSG_DATA(cmsg)) = ddmabuf;940941ret = sendmsg(socket_fd, &msg, MSG_ZEROCOPY);942if (ret < 0)943error(1, errno, "Failed sendmsg");944945fprintf(stderr, "sendmsg_ret=%d\n", ret);946947if (ret != line_size)948error(1, errno, "Did not send all bytes %d vs %zd", ret,949line_size);950951wait_compl(socket_fd);952}953954fprintf(stderr, "%s: tx ok\n", TEST_PREFIX);955956free(line);957close(socket_fd);958959if (ys)960ynl_sock_destroy(ys);961962return 0;963}964965int main(int argc, char *argv[])966{967struct memory_buffer *mem;968int is_server = 0, opt;969int ret;970971while ((opt = getopt(argc, argv, "ls:c:p:v:q:t:f:z:")) != -1) {972switch (opt) {973case 'l':974is_server = 1;975break;976case 's':977server_ip = optarg;978break;979case 'c':980client_ip = optarg;981break;982case 'p':983port = optarg;984break;985case 'v':986do_validation = atoll(optarg);987break;988case 'q':989num_queues = atoi(optarg);990break;991case 't':992start_queue = atoi(optarg);993break;994case 'f':995ifname = optarg;996break;997case 'z':998max_chunk = atoi(optarg);999break;1000case '?':1001fprintf(stderr, "unknown option: %c\n", optopt);1002break;1003}1004}10051006if (!ifname)1007error(1, 0, "Missing -f argument\n");10081009ifindex = if_nametoindex(ifname);10101011fprintf(stderr, "using ifindex=%u\n", ifindex);10121013if (!server_ip && !client_ip) {1014if (start_queue < 0 && num_queues < 0) {1015num_queues = rxq_num(ifindex);1016if (num_queues < 0)1017error(1, 0, "couldn't detect number of queues\n");1018if (num_queues < 2)1019error(1, 0,1020"number of device queues is too low\n");1021/* make sure can bind to multiple queues */1022start_queue = num_queues / 2;1023num_queues /= 2;1024}10251026if (start_queue < 0 || num_queues < 0)1027error(1, 0, "Both -t and -q are required\n");10281029run_devmem_tests();1030return 0;1031}10321033if (start_queue < 0 && num_queues < 0) {1034num_queues = rxq_num(ifindex);1035if (num_queues < 2)1036error(1, 0, "number of device queues is too low\n");10371038num_queues = 1;1039start_queue = rxq_num(ifindex) - num_queues;10401041if (start_queue < 0)1042error(1, 0, "couldn't detect number of queues\n");10431044fprintf(stderr, "using queues %d..%d\n", start_queue, start_queue + num_queues);1045}10461047for (; optind < argc; optind++)1048fprintf(stderr, "extra arguments: %s\n", argv[optind]);10491050if (start_queue < 0)1051error(1, 0, "Missing -t argument\n");10521053if (num_queues < 0)1054error(1, 0, "Missing -q argument\n");10551056if (!server_ip)1057error(1, 0, "Missing -s argument\n");10581059if (!port)1060error(1, 0, "Missing -p argument\n");10611062mem = provider->alloc(getpagesize() * NUM_PAGES);1063ret = is_server ? do_server(mem) : do_client(mem);1064provider->free(mem);10651066return ret;1067}106810691070