Path: blob/master/tools/testing/selftests/drivers/net/hw/ncdevmem.c
29271 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 <stdarg.h>42#include <stdio.h>43#include <stdlib.h>44#include <unistd.h>45#include <stdbool.h>46#include <string.h>47#include <errno.h>48#define __iovec_defined49#include <fcntl.h>50#include <malloc.h>51#include <error.h>52#include <poll.h>5354#include <arpa/inet.h>55#include <sys/socket.h>56#include <sys/mman.h>57#include <sys/ioctl.h>58#include <sys/syscall.h>59#include <sys/time.h>6061#include <linux/memfd.h>62#include <linux/dma-buf.h>63#include <linux/errqueue.h>64#include <linux/udmabuf.h>65#include <linux/types.h>66#include <linux/netlink.h>67#include <linux/genetlink.h>68#include <linux/netdev.h>69#include <linux/ethtool_netlink.h>70#include <time.h>71#include <net/if.h>7273#include "netdev-user.h"74#include "ethtool-user.h"75#include <ynl.h>7677#define PAGE_SHIFT 1278#define TEST_PREFIX "ncdevmem"79#define NUM_PAGES 160008081#ifndef MSG_SOCK_DEVMEM82#define MSG_SOCK_DEVMEM 0x200000083#endif8485#define MAX_IOV 10248687static size_t max_chunk;88static char *server_ip;89static char *client_ip;90static char *port;91static size_t do_validation;92static int start_queue = -1;93static int num_queues = -1;94static char *ifname;95static unsigned int ifindex;96static unsigned int dmabuf_id;97static uint32_t tx_dmabuf_id;98static int waittime_ms = 500;99100/* System state loaded by current_config_load() */101#define MAX_FLOWS 8102static int ntuple_ids[MAX_FLOWS] = { -1, -1, -1, -1, -1, -1, -1, -1, };103104struct memory_buffer {105int fd;106size_t size;107108int devfd;109int memfd;110char *buf_mem;111};112113struct memory_provider {114struct memory_buffer *(*alloc)(size_t size);115void (*free)(struct memory_buffer *ctx);116void (*memcpy_to_device)(struct memory_buffer *dst, size_t off,117void *src, int n);118void (*memcpy_from_device)(void *dst, struct memory_buffer *src,119size_t off, int n);120};121122static void pr_err(const char *fmt, ...)123{124va_list args;125126fprintf(stderr, "%s: ", TEST_PREFIX);127128va_start(args, fmt);129vfprintf(stderr, fmt, args);130va_end(args);131132if (errno != 0)133fprintf(stderr, ": %s", strerror(errno));134fprintf(stderr, "\n");135}136137static struct memory_buffer *udmabuf_alloc(size_t size)138{139struct udmabuf_create create;140struct memory_buffer *ctx;141int ret;142143ctx = malloc(sizeof(*ctx));144if (!ctx)145return NULL;146147ctx->size = size;148149ctx->devfd = open("/dev/udmabuf", O_RDWR);150if (ctx->devfd < 0) {151pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");152goto err_free_ctx;153}154155ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING);156if (ctx->memfd < 0) {157pr_err("[skip,no-memfd]");158goto err_close_dev;159}160161ret = fcntl(ctx->memfd, F_ADD_SEALS, F_SEAL_SHRINK);162if (ret < 0) {163pr_err("[skip,fcntl-add-seals]");164goto err_close_memfd;165}166167ret = ftruncate(ctx->memfd, size);168if (ret == -1) {169pr_err("[FAIL,memfd-truncate]");170goto err_close_memfd;171}172173memset(&create, 0, sizeof(create));174175create.memfd = ctx->memfd;176create.offset = 0;177create.size = size;178ctx->fd = ioctl(ctx->devfd, UDMABUF_CREATE, &create);179if (ctx->fd < 0) {180pr_err("[FAIL, create udmabuf]");181goto err_close_fd;182}183184ctx->buf_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,185ctx->fd, 0);186if (ctx->buf_mem == MAP_FAILED) {187pr_err("[FAIL, map udmabuf]");188goto err_close_fd;189}190191return ctx;192193err_close_fd:194close(ctx->fd);195err_close_memfd:196close(ctx->memfd);197err_close_dev:198close(ctx->devfd);199err_free_ctx:200free(ctx);201return NULL;202}203204static void udmabuf_free(struct memory_buffer *ctx)205{206munmap(ctx->buf_mem, ctx->size);207close(ctx->fd);208close(ctx->memfd);209close(ctx->devfd);210free(ctx);211}212213static void udmabuf_memcpy_to_device(struct memory_buffer *dst, size_t off,214void *src, int n)215{216struct dma_buf_sync sync = {};217218sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;219ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);220221memcpy(dst->buf_mem + off, src, n);222223sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;224ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);225}226227static void udmabuf_memcpy_from_device(void *dst, struct memory_buffer *src,228size_t off, int n)229{230struct dma_buf_sync sync = {};231232sync.flags = DMA_BUF_SYNC_START;233ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);234235memcpy(dst, src->buf_mem + off, n);236237sync.flags = DMA_BUF_SYNC_END;238ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);239}240241static struct memory_provider udmabuf_memory_provider = {242.alloc = udmabuf_alloc,243.free = udmabuf_free,244.memcpy_to_device = udmabuf_memcpy_to_device,245.memcpy_from_device = udmabuf_memcpy_from_device,246};247248static struct memory_provider *provider = &udmabuf_memory_provider;249250static void print_nonzero_bytes(void *ptr, size_t size)251{252unsigned char *p = ptr;253unsigned int i;254255for (i = 0; i < size; i++)256putchar(p[i]);257}258259int validate_buffer(void *line, size_t size)260{261static unsigned char seed = 1;262unsigned char *ptr = line;263unsigned char expected;264static int errors;265size_t i;266267for (i = 0; i < size; i++) {268expected = seed ? seed : '\n';269if (ptr[i] != expected) {270fprintf(stderr,271"Failed validation: expected=%u, actual=%u, index=%lu\n",272expected, ptr[i], i);273errors++;274if (errors > 20) {275pr_err("validation failed");276return -1;277}278}279seed++;280if (seed == do_validation)281seed = 0;282}283284fprintf(stdout, "Validated buffer\n");285return 0;286}287288static int289__run_command(char *out, size_t outlen, const char *cmd, va_list args)290{291char command[256];292FILE *fp;293294vsnprintf(command, sizeof(command), cmd, args);295296fprintf(stderr, "Running: %s\n", command);297fp = popen(command, "r");298if (!fp)299return -1;300if (out) {301size_t len;302303if (!fgets(out, outlen, fp))304return -1;305306/* Remove trailing newline if present */307len = strlen(out);308if (len && out[len - 1] == '\n')309out[len - 1] = '\0';310}311return pclose(fp);312}313314static int run_command(const char *cmd, ...)315{316va_list args;317int ret;318319va_start(args, cmd);320ret = __run_command(NULL, 0, cmd, args);321va_end(args);322323return ret;324}325326static int ethtool_add_flow(const char *format, ...)327{328char local_output[256], cmd[256];329const char *id_start;330int flow_idx, ret;331char *endptr;332long flow_id;333va_list args;334335for (flow_idx = 0; flow_idx < MAX_FLOWS; flow_idx++)336if (ntuple_ids[flow_idx] == -1)337break;338if (flow_idx == MAX_FLOWS) {339fprintf(stderr, "Error: too many flows\n");340return -1;341}342343snprintf(cmd, sizeof(cmd), "ethtool -N %s %s", ifname, format);344345va_start(args, format);346ret = __run_command(local_output, sizeof(local_output), cmd, args);347va_end(args);348349if (ret != 0)350return ret;351352/* Extract the ID from the output */353id_start = strstr(local_output, "Added rule with ID ");354if (!id_start)355return -1;356id_start += strlen("Added rule with ID ");357358flow_id = strtol(id_start, &endptr, 10);359if (endptr == id_start || flow_id < 0 || flow_id > INT_MAX)360return -1;361362fprintf(stderr, "Added flow rule with ID %ld\n", flow_id);363ntuple_ids[flow_idx] = flow_id;364return flow_id;365}366367static int rxq_num(int ifindex)368{369struct ethtool_channels_get_req *req;370struct ethtool_channels_get_rsp *rsp;371struct ynl_error yerr;372struct ynl_sock *ys;373int num = -1;374375ys = ynl_sock_create(&ynl_ethtool_family, &yerr);376if (!ys) {377fprintf(stderr, "YNL: %s\n", yerr.msg);378return -1;379}380381req = ethtool_channels_get_req_alloc();382ethtool_channels_get_req_set_header_dev_index(req, ifindex);383rsp = ethtool_channels_get(ys, req);384if (rsp)385num = rsp->rx_count + rsp->combined_count;386ethtool_channels_get_req_free(req);387ethtool_channels_get_rsp_free(rsp);388389ynl_sock_destroy(ys);390391return num;392}393394static void reset_flow_steering(void)395{396int i;397398for (i = 0; i < MAX_FLOWS; i++) {399if (ntuple_ids[i] == -1)400continue;401run_command("ethtool -N %s delete %d",402ifname, ntuple_ids[i]);403ntuple_ids[i] = -1;404}405}406407static const char *tcp_data_split_str(int val)408{409switch (val) {410case 0:411return "off";412case 1:413return "auto";414case 2:415return "on";416default:417return "?";418}419}420421static struct ethtool_rings_get_rsp *get_ring_config(void)422{423struct ethtool_rings_get_req *get_req;424struct ethtool_rings_get_rsp *get_rsp;425struct ynl_error yerr;426struct ynl_sock *ys;427428ys = ynl_sock_create(&ynl_ethtool_family, &yerr);429if (!ys) {430fprintf(stderr, "YNL: %s\n", yerr.msg);431return NULL;432}433434get_req = ethtool_rings_get_req_alloc();435ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);436get_rsp = ethtool_rings_get(ys, get_req);437ethtool_rings_get_req_free(get_req);438439ynl_sock_destroy(ys);440441return get_rsp;442}443444static void restore_ring_config(const struct ethtool_rings_get_rsp *config)445{446struct ethtool_rings_get_req *get_req;447struct ethtool_rings_get_rsp *get_rsp;448struct ethtool_rings_set_req *req;449struct ynl_error yerr;450struct ynl_sock *ys;451int ret;452453if (!config)454return;455456ys = ynl_sock_create(&ynl_ethtool_family, &yerr);457if (!ys) {458fprintf(stderr, "YNL: %s\n", yerr.msg);459return;460}461462req = ethtool_rings_set_req_alloc();463ethtool_rings_set_req_set_header_dev_index(req, ifindex);464ethtool_rings_set_req_set_tcp_data_split(req,465ETHTOOL_TCP_DATA_SPLIT_UNKNOWN);466if (config->_present.hds_thresh)467ethtool_rings_set_req_set_hds_thresh(req, config->hds_thresh);468469ret = ethtool_rings_set(ys, req);470if (ret < 0)471fprintf(stderr, "YNL restoring HDS cfg: %s\n", ys->err.msg);472473get_req = ethtool_rings_get_req_alloc();474ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);475get_rsp = ethtool_rings_get(ys, get_req);476ethtool_rings_get_req_free(get_req);477478/* use explicit value if UKNOWN didn't give us the previous */479if (get_rsp->tcp_data_split != config->tcp_data_split) {480ethtool_rings_set_req_set_tcp_data_split(req,481config->tcp_data_split);482ret = ethtool_rings_set(ys, req);483if (ret < 0)484fprintf(stderr, "YNL restoring expl HDS cfg: %s\n",485ys->err.msg);486}487488ethtool_rings_get_rsp_free(get_rsp);489ethtool_rings_set_req_free(req);490491ynl_sock_destroy(ys);492}493494static int495configure_headersplit(const struct ethtool_rings_get_rsp *old, bool on)496{497struct ethtool_rings_get_req *get_req;498struct ethtool_rings_get_rsp *get_rsp;499struct ethtool_rings_set_req *req;500struct ynl_error yerr;501struct ynl_sock *ys;502int ret;503504ys = ynl_sock_create(&ynl_ethtool_family, &yerr);505if (!ys) {506fprintf(stderr, "YNL: %s\n", yerr.msg);507return -1;508}509510req = ethtool_rings_set_req_alloc();511ethtool_rings_set_req_set_header_dev_index(req, ifindex);512if (on) {513ethtool_rings_set_req_set_tcp_data_split(req,514ETHTOOL_TCP_DATA_SPLIT_ENABLED);515if (old->_present.hds_thresh)516ethtool_rings_set_req_set_hds_thresh(req, 0);517} else {518ethtool_rings_set_req_set_tcp_data_split(req,519ETHTOOL_TCP_DATA_SPLIT_UNKNOWN);520}521ret = ethtool_rings_set(ys, req);522if (ret < 0)523fprintf(stderr, "YNL failed: %s\n", ys->err.msg);524ethtool_rings_set_req_free(req);525526if (ret == 0) {527get_req = ethtool_rings_get_req_alloc();528ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);529get_rsp = ethtool_rings_get(ys, get_req);530ethtool_rings_get_req_free(get_req);531if (get_rsp)532fprintf(stderr, "TCP header split: %s\n",533tcp_data_split_str(get_rsp->tcp_data_split));534ethtool_rings_get_rsp_free(get_rsp);535}536537ynl_sock_destroy(ys);538539return ret;540}541542static int configure_rss(void)543{544return run_command("ethtool -X %s equal %d >&2", ifname, start_queue);545}546547static void reset_rss(void)548{549run_command("ethtool -X %s default >&2", ifname, start_queue);550}551552static int check_changing_channels(unsigned int rx, unsigned int tx)553{554struct ethtool_channels_get_req *gchan;555struct ethtool_channels_set_req *schan;556struct ethtool_channels_get_rsp *chan;557struct ynl_error yerr;558struct ynl_sock *ys;559int ret;560561fprintf(stderr, "setting channel count rx:%u tx:%u\n", rx, tx);562563ys = ynl_sock_create(&ynl_ethtool_family, &yerr);564if (!ys) {565fprintf(stderr, "YNL: %s\n", yerr.msg);566return -1;567}568569gchan = ethtool_channels_get_req_alloc();570if (!gchan) {571ret = -1;572goto exit_close_sock;573}574575ethtool_channels_get_req_set_header_dev_index(gchan, ifindex);576chan = ethtool_channels_get(ys, gchan);577ethtool_channels_get_req_free(gchan);578if (!chan) {579fprintf(stderr, "YNL get channels: %s\n", ys->err.msg);580ret = -1;581goto exit_close_sock;582}583584schan = ethtool_channels_set_req_alloc();585if (!schan) {586ret = -1;587goto exit_free_chan;588}589590ethtool_channels_set_req_set_header_dev_index(schan, ifindex);591592if (chan->_present.combined_count) {593if (chan->_present.rx_count || chan->_present.tx_count) {594ethtool_channels_set_req_set_rx_count(schan, 0);595ethtool_channels_set_req_set_tx_count(schan, 0);596}597598if (rx == tx) {599ethtool_channels_set_req_set_combined_count(schan, rx);600} else if (rx > tx) {601ethtool_channels_set_req_set_combined_count(schan, tx);602ethtool_channels_set_req_set_rx_count(schan, rx - tx);603} else {604ethtool_channels_set_req_set_combined_count(schan, rx);605ethtool_channels_set_req_set_tx_count(schan, tx - rx);606}607608} else if (chan->_present.rx_count) {609ethtool_channels_set_req_set_rx_count(schan, rx);610ethtool_channels_set_req_set_tx_count(schan, tx);611} else {612fprintf(stderr, "Error: device has neither combined nor rx channels\n");613ret = -1;614goto exit_free_schan;615}616617ret = ethtool_channels_set(ys, schan);618if (ret) {619fprintf(stderr, "YNL set channels: %s\n", ys->err.msg);620} else {621/* We were expecting a failure, go back to previous settings */622ethtool_channels_set_req_set_combined_count(schan,623chan->combined_count);624ethtool_channels_set_req_set_rx_count(schan, chan->rx_count);625ethtool_channels_set_req_set_tx_count(schan, chan->tx_count);626627ret = ethtool_channels_set(ys, schan);628if (ret)629fprintf(stderr, "YNL un-setting channels: %s\n",630ys->err.msg);631}632633exit_free_schan:634ethtool_channels_set_req_free(schan);635exit_free_chan:636ethtool_channels_get_rsp_free(chan);637exit_close_sock:638ynl_sock_destroy(ys);639640return ret;641}642643static int configure_flow_steering(struct sockaddr_in6 *server_sin)644{645const char *type = "tcp6";646const char *server_addr;647char buf[40];648int flow_id;649650inet_ntop(AF_INET6, &server_sin->sin6_addr, buf, sizeof(buf));651server_addr = buf;652653if (IN6_IS_ADDR_V4MAPPED(&server_sin->sin6_addr)) {654type = "tcp4";655server_addr = strrchr(server_addr, ':') + 1;656}657658/* Try configure 5-tuple */659flow_id = ethtool_add_flow("flow-type %s %s %s dst-ip %s %s %s dst-port %s queue %d",660type,661client_ip ? "src-ip" : "",662client_ip ?: "",663server_addr,664client_ip ? "src-port" : "",665client_ip ? port : "",666port, start_queue);667if (flow_id < 0) {668/* If that fails, try configure 3-tuple */669flow_id = ethtool_add_flow("flow-type %s dst-ip %s dst-port %s queue %d",670type, server_addr, port, start_queue);671if (flow_id < 0)672/* If that fails, return error */673return -1;674}675676return 0;677}678679static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd,680struct netdev_queue_id *queues,681unsigned int n_queue_index, struct ynl_sock **ys)682{683struct netdev_bind_rx_req *req = NULL;684struct netdev_bind_rx_rsp *rsp = NULL;685struct ynl_error yerr;686687*ys = ynl_sock_create(&ynl_netdev_family, &yerr);688if (!*ys) {689netdev_queue_id_free(queues);690fprintf(stderr, "YNL: %s\n", yerr.msg);691return -1;692}693694req = netdev_bind_rx_req_alloc();695netdev_bind_rx_req_set_ifindex(req, ifindex);696netdev_bind_rx_req_set_fd(req, dmabuf_fd);697__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);698699rsp = netdev_bind_rx(*ys, req);700if (!rsp) {701perror("netdev_bind_rx");702goto err_close;703}704705if (!rsp->_present.id) {706perror("id not present");707goto err_close;708}709710fprintf(stderr, "got dmabuf id=%d\n", rsp->id);711dmabuf_id = rsp->id;712713netdev_bind_rx_req_free(req);714netdev_bind_rx_rsp_free(rsp);715716return 0;717718err_close:719fprintf(stderr, "YNL failed: %s\n", (*ys)->err.msg);720netdev_bind_rx_req_free(req);721ynl_sock_destroy(*ys);722return -1;723}724725static int bind_tx_queue(unsigned int ifindex, unsigned int dmabuf_fd,726struct ynl_sock **ys)727{728struct netdev_bind_tx_req *req = NULL;729struct netdev_bind_tx_rsp *rsp = NULL;730struct ynl_error yerr;731732*ys = ynl_sock_create(&ynl_netdev_family, &yerr);733if (!*ys) {734fprintf(stderr, "YNL: %s\n", yerr.msg);735return -1;736}737738req = netdev_bind_tx_req_alloc();739netdev_bind_tx_req_set_ifindex(req, ifindex);740netdev_bind_tx_req_set_fd(req, dmabuf_fd);741742rsp = netdev_bind_tx(*ys, req);743if (!rsp) {744perror("netdev_bind_tx");745goto err_close;746}747748if (!rsp->_present.id) {749perror("id not present");750goto err_close;751}752753fprintf(stderr, "got tx dmabuf id=%d\n", rsp->id);754tx_dmabuf_id = rsp->id;755756netdev_bind_tx_req_free(req);757netdev_bind_tx_rsp_free(rsp);758759return 0;760761err_close:762fprintf(stderr, "YNL failed: %s\n", (*ys)->err.msg);763netdev_bind_tx_req_free(req);764ynl_sock_destroy(*ys);765return -1;766}767768static int enable_reuseaddr(int fd)769{770int opt = 1;771int ret;772773ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));774if (ret) {775pr_err("SO_REUSEPORT failed");776return -1;777}778779ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));780if (ret) {781pr_err("SO_REUSEADDR failed");782return -1;783}784785return 0;786}787788static int parse_address(const char *str, int port, struct sockaddr_in6 *sin6)789{790int ret;791792sin6->sin6_family = AF_INET6;793sin6->sin6_port = htons(port);794795ret = inet_pton(sin6->sin6_family, str, &sin6->sin6_addr);796if (ret != 1) {797/* fallback to plain IPv4 */798ret = inet_pton(AF_INET, str, &sin6->sin6_addr.s6_addr32[3]);799if (ret != 1)800return -1;801802/* add ::ffff prefix */803sin6->sin6_addr.s6_addr32[0] = 0;804sin6->sin6_addr.s6_addr32[1] = 0;805sin6->sin6_addr.s6_addr16[4] = 0;806sin6->sin6_addr.s6_addr16[5] = 0xffff;807}808809return 0;810}811812static struct netdev_queue_id *create_queues(void)813{814struct netdev_queue_id *queues;815size_t i = 0;816817queues = netdev_queue_id_alloc(num_queues);818for (i = 0; i < num_queues; i++) {819netdev_queue_id_set_type(&queues[i], NETDEV_QUEUE_TYPE_RX);820netdev_queue_id_set_id(&queues[i], start_queue + i);821}822823return queues;824}825826static int do_server(struct memory_buffer *mem)827{828struct ethtool_rings_get_rsp *ring_config;829char ctrl_data[sizeof(int) * 20000];830size_t non_page_aligned_frags = 0;831struct sockaddr_in6 client_addr;832struct sockaddr_in6 server_sin;833size_t page_aligned_frags = 0;834size_t total_received = 0;835socklen_t client_addr_len;836bool is_devmem = false;837char *tmp_mem = NULL;838struct ynl_sock *ys;839char iobuf[819200];840int ret, err = -1;841char buffer[256];842int socket_fd;843int client_fd;844845ret = parse_address(server_ip, atoi(port), &server_sin);846if (ret < 0) {847pr_err("parse server address");848return -1;849}850851ring_config = get_ring_config();852if (!ring_config) {853pr_err("Failed to get current ring configuration");854return -1;855}856857if (configure_headersplit(ring_config, 1)) {858pr_err("Failed to enable TCP header split");859goto err_free_ring_config;860}861862/* Configure RSS to divert all traffic from our devmem queues */863if (configure_rss()) {864pr_err("Failed to configure rss");865goto err_reset_headersplit;866}867868/* Flow steer our devmem flows to start_queue */869if (configure_flow_steering(&server_sin)) {870pr_err("Failed to configure flow steering");871goto err_reset_rss;872}873874if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys)) {875pr_err("Failed to bind");876goto err_reset_flow_steering;877}878879tmp_mem = malloc(mem->size);880if (!tmp_mem)881goto err_unbind;882883socket_fd = socket(AF_INET6, SOCK_STREAM, 0);884if (socket_fd < 0) {885pr_err("Failed to create socket");886goto err_free_tmp;887}888889if (enable_reuseaddr(socket_fd))890goto err_close_socket;891892fprintf(stderr, "binding to address %s:%d\n", server_ip,893ntohs(server_sin.sin6_port));894895ret = bind(socket_fd, &server_sin, sizeof(server_sin));896if (ret) {897pr_err("Failed to bind");898goto err_close_socket;899}900901ret = listen(socket_fd, 1);902if (ret) {903pr_err("Failed to listen");904goto err_close_socket;905}906907client_addr_len = sizeof(client_addr);908909inet_ntop(AF_INET6, &server_sin.sin6_addr, buffer,910sizeof(buffer));911fprintf(stderr, "Waiting or connection on %s:%d\n", buffer,912ntohs(server_sin.sin6_port));913client_fd = accept(socket_fd, &client_addr, &client_addr_len);914if (client_fd < 0) {915pr_err("Failed to accept");916goto err_close_socket;917}918919inet_ntop(AF_INET6, &client_addr.sin6_addr, buffer,920sizeof(buffer));921fprintf(stderr, "Got connection from %s:%d\n", buffer,922ntohs(client_addr.sin6_port));923924while (1) {925struct iovec iov = { .iov_base = iobuf,926.iov_len = sizeof(iobuf) };927struct dmabuf_cmsg *dmabuf_cmsg = NULL;928struct cmsghdr *cm = NULL;929struct msghdr msg = { 0 };930struct dmabuf_token token;931ssize_t ret;932933is_devmem = false;934935msg.msg_iov = &iov;936msg.msg_iovlen = 1;937msg.msg_control = ctrl_data;938msg.msg_controllen = sizeof(ctrl_data);939ret = recvmsg(client_fd, &msg, MSG_SOCK_DEVMEM);940fprintf(stderr, "recvmsg ret=%ld\n", ret);941if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))942continue;943if (ret < 0) {944perror("recvmsg");945if (errno == EFAULT) {946pr_err("received EFAULT, won't recover");947goto err_close_client;948}949continue;950}951if (ret == 0) {952errno = 0;953pr_err("client exited");954goto cleanup;955}956957for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {958if (cm->cmsg_level != SOL_SOCKET ||959(cm->cmsg_type != SCM_DEVMEM_DMABUF &&960cm->cmsg_type != SCM_DEVMEM_LINEAR)) {961fprintf(stderr, "skipping non-devmem cmsg\n");962continue;963}964965dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);966is_devmem = true;967968if (cm->cmsg_type == SCM_DEVMEM_LINEAR) {969/* TODO: process data copied from skb's linear970* buffer.971*/972fprintf(stderr,973"SCM_DEVMEM_LINEAR. dmabuf_cmsg->frag_size=%u\n",974dmabuf_cmsg->frag_size);975976continue;977}978979token.token_start = dmabuf_cmsg->frag_token;980token.token_count = 1;981982total_received += dmabuf_cmsg->frag_size;983fprintf(stderr,984"received frag_page=%llu, in_page_offset=%llu, frag_offset=%llu, frag_size=%u, token=%u, total_received=%lu, dmabuf_id=%u\n",985dmabuf_cmsg->frag_offset >> PAGE_SHIFT,986dmabuf_cmsg->frag_offset % getpagesize(),987dmabuf_cmsg->frag_offset,988dmabuf_cmsg->frag_size, dmabuf_cmsg->frag_token,989total_received, dmabuf_cmsg->dmabuf_id);990991if (dmabuf_cmsg->dmabuf_id != dmabuf_id) {992pr_err("received on wrong dmabuf_id: flow steering error");993goto err_close_client;994}995996if (dmabuf_cmsg->frag_size % getpagesize())997non_page_aligned_frags++;998else999page_aligned_frags++;10001001provider->memcpy_from_device(tmp_mem, mem,1002dmabuf_cmsg->frag_offset,1003dmabuf_cmsg->frag_size);10041005if (do_validation) {1006if (validate_buffer(tmp_mem,1007dmabuf_cmsg->frag_size))1008goto err_close_client;1009} else {1010print_nonzero_bytes(tmp_mem,1011dmabuf_cmsg->frag_size);1012}10131014ret = setsockopt(client_fd, SOL_SOCKET,1015SO_DEVMEM_DONTNEED, &token,1016sizeof(token));1017if (ret != 1) {1018pr_err("SO_DEVMEM_DONTNEED not enough tokens");1019goto err_close_client;1020}1021}1022if (!is_devmem) {1023pr_err("flow steering error");1024goto err_close_client;1025}10261027fprintf(stderr, "total_received=%lu\n", total_received);1028}10291030fprintf(stderr, "%s: ok\n", TEST_PREFIX);10311032fprintf(stderr, "page_aligned_frags=%lu, non_page_aligned_frags=%lu\n",1033page_aligned_frags, non_page_aligned_frags);10341035cleanup:1036err = 0;10371038err_close_client:1039close(client_fd);1040err_close_socket:1041close(socket_fd);1042err_free_tmp:1043free(tmp_mem);1044err_unbind:1045ynl_sock_destroy(ys);1046err_reset_flow_steering:1047reset_flow_steering();1048err_reset_rss:1049reset_rss();1050err_reset_headersplit:1051restore_ring_config(ring_config);1052err_free_ring_config:1053ethtool_rings_get_rsp_free(ring_config);1054return err;1055}10561057int run_devmem_tests(void)1058{1059struct ethtool_rings_get_rsp *ring_config;1060struct netdev_queue_id *queues;1061struct memory_buffer *mem;1062struct ynl_sock *ys;1063int err = -1;10641065mem = provider->alloc(getpagesize() * NUM_PAGES);1066if (!mem) {1067pr_err("Failed to allocate memory buffer");1068return -1;1069}10701071ring_config = get_ring_config();1072if (!ring_config) {1073pr_err("Failed to get current ring configuration");1074goto err_free_mem;1075}10761077/* Configure RSS to divert all traffic from our devmem queues */1078if (configure_rss()) {1079pr_err("rss error");1080goto err_free_ring_config;1081}10821083if (configure_headersplit(ring_config, 1)) {1084pr_err("Failed to configure header split");1085goto err_reset_rss;1086}10871088queues = netdev_queue_id_alloc(num_queues);1089if (!queues) {1090pr_err("Failed to allocate empty queues array");1091goto err_reset_headersplit;1092}10931094if (!bind_rx_queue(ifindex, mem->fd, queues, num_queues, &ys)) {1095pr_err("Binding empty queues array should have failed");1096goto err_unbind;1097}10981099if (configure_headersplit(ring_config, 0)) {1100pr_err("Failed to configure header split");1101goto err_reset_headersplit;1102}11031104queues = create_queues();1105if (!queues) {1106pr_err("Failed to create queues");1107goto err_reset_headersplit;1108}11091110if (!bind_rx_queue(ifindex, mem->fd, queues, num_queues, &ys)) {1111pr_err("Configure dmabuf with header split off should have failed");1112goto err_unbind;1113}11141115if (configure_headersplit(ring_config, 1)) {1116pr_err("Failed to configure header split");1117goto err_reset_headersplit;1118}11191120queues = create_queues();1121if (!queues) {1122pr_err("Failed to create queues");1123goto err_reset_headersplit;1124}11251126if (bind_rx_queue(ifindex, mem->fd, queues, num_queues, &ys)) {1127pr_err("Failed to bind");1128goto err_reset_headersplit;1129}11301131/* Deactivating a bound queue should not be legal */1132if (!check_changing_channels(num_queues, num_queues)) {1133pr_err("Deactivating a bound queue should be illegal");1134goto err_unbind;1135}11361137err = 0;1138goto err_unbind;11391140err_unbind:1141ynl_sock_destroy(ys);1142err_reset_headersplit:1143restore_ring_config(ring_config);1144err_reset_rss:1145reset_rss();1146err_free_ring_config:1147ethtool_rings_get_rsp_free(ring_config);1148err_free_mem:1149provider->free(mem);1150return err;1151}11521153static uint64_t gettimeofday_ms(void)1154{1155struct timeval tv;11561157gettimeofday(&tv, NULL);1158return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000ULL);1159}11601161static int do_poll(int fd)1162{1163struct pollfd pfd;1164int ret;11651166pfd.revents = 0;1167pfd.fd = fd;11681169ret = poll(&pfd, 1, waittime_ms);1170if (ret == -1) {1171pr_err("poll");1172return -1;1173}11741175return ret && (pfd.revents & POLLERR);1176}11771178static int wait_compl(int fd)1179{1180int64_t tstop = gettimeofday_ms() + waittime_ms;1181char control[CMSG_SPACE(100)] = {};1182struct sock_extended_err *serr;1183struct msghdr msg = {};1184struct cmsghdr *cm;1185__u32 hi, lo;1186int ret;11871188msg.msg_control = control;1189msg.msg_controllen = sizeof(control);11901191while (gettimeofday_ms() < tstop) {1192ret = do_poll(fd);1193if (ret < 0)1194return ret;1195if (!ret)1196continue;11971198ret = recvmsg(fd, &msg, MSG_ERRQUEUE);1199if (ret < 0) {1200if (errno == EAGAIN)1201continue;1202pr_err("recvmsg(MSG_ERRQUEUE)");1203return -1;1204}1205if (msg.msg_flags & MSG_CTRUNC) {1206pr_err("MSG_CTRUNC");1207return -1;1208}12091210for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {1211if (cm->cmsg_level != SOL_IP &&1212cm->cmsg_level != SOL_IPV6)1213continue;1214if (cm->cmsg_level == SOL_IP &&1215cm->cmsg_type != IP_RECVERR)1216continue;1217if (cm->cmsg_level == SOL_IPV6 &&1218cm->cmsg_type != IPV6_RECVERR)1219continue;12201221serr = (void *)CMSG_DATA(cm);1222if (serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY) {1223pr_err("wrong origin %u", serr->ee_origin);1224return -1;1225}1226if (serr->ee_errno != 0) {1227pr_err("wrong errno %d", serr->ee_errno);1228return -1;1229}12301231hi = serr->ee_data;1232lo = serr->ee_info;12331234fprintf(stderr, "tx complete [%d,%d]\n", lo, hi);1235return 0;1236}1237}12381239pr_err("did not receive tx completion");1240return -1;1241}12421243static int do_client(struct memory_buffer *mem)1244{1245char ctrl_data[CMSG_SPACE(sizeof(__u32))];1246struct sockaddr_in6 server_sin;1247struct sockaddr_in6 client_sin;1248struct ynl_sock *ys = NULL;1249struct iovec iov[MAX_IOV];1250struct msghdr msg = {};1251ssize_t line_size = 0;1252struct cmsghdr *cmsg;1253char *line = NULL;1254int ret, err = -1;1255size_t len = 0;1256int socket_fd;1257__u32 ddmabuf;1258int opt = 1;12591260ret = parse_address(server_ip, atoi(port), &server_sin);1261if (ret < 0) {1262pr_err("parse server address");1263return -1;1264}12651266if (client_ip) {1267ret = parse_address(client_ip, atoi(port), &client_sin);1268if (ret < 0) {1269pr_err("parse client address");1270return ret;1271}1272}12731274socket_fd = socket(AF_INET6, SOCK_STREAM, 0);1275if (socket_fd < 0) {1276pr_err("create socket");1277return -1;1278}12791280if (enable_reuseaddr(socket_fd))1281goto err_close_socket;12821283ret = setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,1284strlen(ifname) + 1);1285if (ret) {1286pr_err("bindtodevice");1287goto err_close_socket;1288}12891290if (bind_tx_queue(ifindex, mem->fd, &ys)) {1291pr_err("Failed to bind");1292goto err_close_socket;1293}12941295if (client_ip) {1296ret = bind(socket_fd, &client_sin, sizeof(client_sin));1297if (ret) {1298pr_err("bind");1299goto err_unbind;1300}1301}13021303ret = setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &opt, sizeof(opt));1304if (ret) {1305pr_err("set sock opt");1306goto err_unbind;1307}13081309fprintf(stderr, "Connect to %s %d (via %s)\n", server_ip,1310ntohs(server_sin.sin6_port), ifname);13111312ret = connect(socket_fd, &server_sin, sizeof(server_sin));1313if (ret) {1314pr_err("connect");1315goto err_unbind;1316}13171318while (1) {1319free(line);1320line = NULL;1321line_size = getline(&line, &len, stdin);13221323if (line_size < 0)1324break;13251326if (max_chunk) {1327msg.msg_iovlen =1328(line_size + max_chunk - 1) / max_chunk;1329if (msg.msg_iovlen > MAX_IOV) {1330pr_err("can't partition %zd bytes into maximum of %d chunks",1331line_size, MAX_IOV);1332goto err_free_line;1333}13341335for (int i = 0; i < msg.msg_iovlen; i++) {1336iov[i].iov_base = (void *)(i * max_chunk);1337iov[i].iov_len = max_chunk;1338}13391340iov[msg.msg_iovlen - 1].iov_len =1341line_size - (msg.msg_iovlen - 1) * max_chunk;1342} else {1343iov[0].iov_base = 0;1344iov[0].iov_len = line_size;1345msg.msg_iovlen = 1;1346}13471348msg.msg_iov = iov;1349provider->memcpy_to_device(mem, 0, line, line_size);13501351msg.msg_control = ctrl_data;1352msg.msg_controllen = sizeof(ctrl_data);13531354cmsg = CMSG_FIRSTHDR(&msg);1355cmsg->cmsg_level = SOL_SOCKET;1356cmsg->cmsg_type = SCM_DEVMEM_DMABUF;1357cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));13581359ddmabuf = tx_dmabuf_id;13601361*((__u32 *)CMSG_DATA(cmsg)) = ddmabuf;13621363ret = sendmsg(socket_fd, &msg, MSG_ZEROCOPY);1364if (ret < 0) {1365pr_err("Failed sendmsg");1366goto err_free_line;1367}13681369fprintf(stderr, "sendmsg_ret=%d\n", ret);13701371if (ret != line_size) {1372pr_err("Did not send all bytes %d vs %zd", ret, line_size);1373goto err_free_line;1374}13751376if (wait_compl(socket_fd))1377goto err_free_line;1378}13791380fprintf(stderr, "%s: tx ok\n", TEST_PREFIX);13811382err = 0;13831384err_free_line:1385free(line);1386err_unbind:1387ynl_sock_destroy(ys);1388err_close_socket:1389close(socket_fd);1390return err;1391}13921393int main(int argc, char *argv[])1394{1395struct memory_buffer *mem;1396int is_server = 0, opt;1397int ret, err = 1;13981399while ((opt = getopt(argc, argv, "ls:c:p:v:q:t:f:z:")) != -1) {1400switch (opt) {1401case 'l':1402is_server = 1;1403break;1404case 's':1405server_ip = optarg;1406break;1407case 'c':1408client_ip = optarg;1409break;1410case 'p':1411port = optarg;1412break;1413case 'v':1414do_validation = atoll(optarg);1415break;1416case 'q':1417num_queues = atoi(optarg);1418break;1419case 't':1420start_queue = atoi(optarg);1421break;1422case 'f':1423ifname = optarg;1424break;1425case 'z':1426max_chunk = atoi(optarg);1427break;1428case '?':1429fprintf(stderr, "unknown option: %c\n", optopt);1430break;1431}1432}14331434if (!ifname) {1435pr_err("Missing -f argument");1436return 1;1437}14381439ifindex = if_nametoindex(ifname);14401441fprintf(stderr, "using ifindex=%u\n", ifindex);14421443if (!server_ip && !client_ip) {1444if (start_queue < 0 && num_queues < 0) {1445num_queues = rxq_num(ifindex);1446if (num_queues < 0) {1447pr_err("couldn't detect number of queues");1448return 1;1449}1450if (num_queues < 2) {1451pr_err("number of device queues is too low");1452return 1;1453}1454/* make sure can bind to multiple queues */1455start_queue = num_queues / 2;1456num_queues /= 2;1457}14581459if (start_queue < 0 || num_queues < 0) {1460pr_err("Both -t and -q are required");1461return 1;1462}14631464return run_devmem_tests();1465}14661467if (start_queue < 0 && num_queues < 0) {1468num_queues = rxq_num(ifindex);1469if (num_queues < 2) {1470pr_err("number of device queues is too low");1471return 1;1472}14731474num_queues = 1;1475start_queue = rxq_num(ifindex) - num_queues;14761477if (start_queue < 0) {1478pr_err("couldn't detect number of queues");1479return 1;1480}14811482fprintf(stderr, "using queues %d..%d\n", start_queue, start_queue + num_queues);1483}14841485for (; optind < argc; optind++)1486fprintf(stderr, "extra arguments: %s\n", argv[optind]);14871488if (start_queue < 0) {1489pr_err("Missing -t argument");1490return 1;1491}14921493if (num_queues < 0) {1494pr_err("Missing -q argument");1495return 1;1496}14971498if (!server_ip) {1499pr_err("Missing -s argument");1500return 1;1501}15021503if (!port) {1504pr_err("Missing -p argument");1505return 1;1506}15071508mem = provider->alloc(getpagesize() * NUM_PAGES);1509if (!mem) {1510pr_err("Failed to allocate memory buffer");1511return 1;1512}15131514ret = is_server ? do_server(mem) : do_client(mem);1515if (ret)1516goto err_free_mem;15171518err = 0;15191520err_free_mem:1521provider->free(mem);1522return err;1523}152415251526