diff --git a/lib/libnetmap/libnetmap.h b/lib/libnetmap/libnetmap.h index 0367a1735c4f..ff03babc04b1 100644 --- a/lib/libnetmap/libnetmap.h +++ b/lib/libnetmap/libnetmap.h @@ -1,660 +1,717 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2018 Universita` di Pisa * All rights reserved. * * 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. * $FreeBSD$ */ #ifndef LIBNETMAP_H_ #define LIBNETMAP_H_ /* if thread-safety is not needed, define LIBNETMAP_NOTHREADSAFE before including * this file. */ /* NOTE: we include net/netmap_user.h without defining NETMAP_WITH_LIBS, which * is deprecated. If you still need it, please define NETMAP_WITH_LIBS and * include net/netmap_user.h before including this file. */ #include struct nmctx; struct nmport_d; struct nmem_d; /* * A port open specification (portspec for brevity) has the following syntax * (square brackets delimit optional parts): * * subsystem:vpname[mode][options] * * The "subsystem" is denoted by a prefix, possibly followed by an identifier. * There can be several kinds of subsystems, each one selected by a unique * prefix. Currently defined subsystems are: * * netmap (no id allowed) * the standard subsystem * * vale (followed by a possibly empty id) * the vpname is connected to a VALE switch identified by * the id (an empty id selects the default switch) * * The "vpname" has the following syntax: * * identifier or * identifier1{identifier2 or * identifier1}identifier2 * * Identifiers are sequences of alphanumeric characters. The part that begins * with either '{' or '}', when present, denotes a netmap pipe opened in the * same memory region as the subsystem:indentifier1 port. * * The "mode" can be one of the following: * * ^ bind all host (sw) ring pairs * ^NN bind individual host ring pair * * bind host and NIC ring pairs * -NN bind individual NIC ring pair * @NN open the port in the NN memory region * a suffix starting with / and the following flags, * in any order: * x exclusive access * z zero copy monitor (both tx and rx) * t monitor tx side (copy monitor) * r monitor rx side (copy monitor) * R bind only RX ring(s) * T bind only TX ring(s) * * The "options" start at the first '@' character not followed by a number. * Each option starts with '@' and has the following syntax: * * option (flag option) * option=value (single key option) * option:key1=value1,key2=value2,... (multi-key option) * * For multi-key options, the keys can be assigned in any order, but they * cannot be assigned more than once. It is not necessary to assign all the * option keys: unmentioned keys will receive default values. Some multi-key * options define a default key and also accept the single-key syntax, by * assigning the value to this key. * * NOTE: Options may be silently ignored if the port is already open by some * other process. * * The currently available options are (default keys, when defined, are marked * with '*'): * * share (single-key) * open the port in the same memory region used by the * given port name (the port name must be given in * subsystem:vpname form) * * conf (multi-key) * specify the rings/slots numbers (effective only on * ports that are created by the open operation itself, * and ignored otherwise). * * The keys are: * * *rings number of tx and rx rings * tx-rings number of tx rings * rx-rings number of rx rings * host-rings number of tx and rx host rings * host-tx-rings number of host tx rings * host-rx-rings number of host rx rings * slots number of slots in each tx and rx * ring * tx-slots number of slots in each tx ring * rx-slots number of slots in each rx ring * * (more specific keys override the less specific ones) * All keys default to zero if not assigned, and the * corresponding value will be chosen by netmap. * * extmem (multi-key) * open the port in the memory region obtained by * mmap()ing the given file. * * The keys are: * * *file the file to mmap * if-num number of pre-allocated netmap_if's * if-size size of each netmap_if * ring-num number of pre-allocated netmap_ring's * ring-size size of each netmap_ring * buf-num number of pre-allocated buffers * buf-size size of each buffer * * file must be assigned. The other keys default to zero, * causing netmap to take the corresponding values from * the priv_{if,ring,buf}_{num,size} sysctls. * + * offset (multi-key) + * reserve (part of) the ptr fields as an offset field + * and write an initial offset into them. + * + * The keys are: + * + * bits number of bits of ptr to use + * *initial initial offset value + * + * initial must be assigned. If bits is omitted, it + * defaults to the entire ptr field. The max offset is set + * at the same value as the initial offset. Note that the + * actual values may be increased by the kernel. + * + * This option is disabled by default (see + * nmport_enable_option() below) */ /* nmport manipulation */ /* struct nmport_d - describes a netmap port */ struct nmport_d { /* see net/netmap.h for the definition of these fields */ struct nmreq_header hdr; struct nmreq_register reg; /* all the fields below should be considered read-only */ /* if the same context is used throughout the program, d1->mem == * d2->mem iff d1 and d2 are using the memory region (i.e., zero * copy is possible between the two ports) */ struct nmem_d *mem; /* the nmctx used when this nmport_d was created */ struct nmctx *ctx; int register_done; /* nmport_register() has been called */ int mmap_done; /* nmport_mmap() has been called */ /* pointer to the extmem option contained in the hdr options, if any */ struct nmreq_opt_extmem *extmem; /* the fields below are compatible with nm_open() */ int fd; /* "/dev/netmap", -1 if not open */ struct netmap_if *nifp; /* pointer to the netmap_if */ uint16_t first_tx_ring; uint16_t last_tx_ring; uint16_t first_rx_ring; uint16_t last_rx_ring; uint16_t cur_tx_ring; /* used by nmport_inject */ uint16_t cur_rx_ring; /* LIFO list of cleanup functions (used internally) */ struct nmport_cleanup_d *clist; }; /* nmport_open - opens a port from a portspec * @portspec the port opening specification * * If successful, the function returns a new nmport_d describing a netmap * port, opened according to the port specification, ready to be used for rx * and/or tx. * * The rings available for tx are in the [first_tx_ring, last_tx_ring] * interval, and similarly for rx. One or both intervals may be empty. * * When done using it, the nmport_d descriptor must be closed using * nmport_close(). * * In case of error, NULL is returned, errno is set to some error, and an * error message is sent through the error() method of the current context. */ struct nmport_d * nmport_open(const char *portspec); /* nport_close - close a netmap port * @d the port we want to close * * Undoes the actions performed by the nmport_open that created d, then * frees the descriptor. */ void nmport_close(struct nmport_d *d); /* nmport_inject - sends a packet * @d the port through which we want to send * @buf base address of the packet * @size its size in bytes * * Sends a packet using the cur_tx_ring and updates the index * to use all available tx rings in turn. Note: the packet is copied. * * Returns 0 on success an -1 on error. */ int nmport_inject(struct nmport_d *d, const void *buf, size_t size); /* * the functions below can be used to split the functionality of * nmport_open when special features (e.g., extra buffers) are needed * * The relation among the functions is as follows: * * |nmport_new * |nmport_prepare = | * | |nmport_parse * nmport_open =| * | |nmport_register * |nmport_open_desc =| * |nmport_mmap * */ /* nmport_new - create a new nmport_d * * Creates a new nmport_d using the malloc() method of the current default * context. Returns NULL on error, setting errno to an error value. */ struct nmport_d *nmport_new(void); /* nmport_parse - fills the nmport_d netmap-register request * @d the nmport to be filled * @portspec the port opening specification * * This function parses the portspec and initizalizes the @d->hdr and @d->reg * fields. It may need to allocate a list of options. If an extmem option is * found, it may also mmap() the corresponding file. * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_parse(struct nmport_d *d, const char *portspec); /* nmport_register - registers the port with netmap * @d the nmport to be registered * * This function obtains a netmap file descriptor and registers the port with * netmap. The @d->hdr and @d->reg data structures must have been previously * initialized (via nmport_parse() or otherwise). * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_register(struct nmport_d *); /* nmport_mmap - maps the port resources into the process memory * @d the nmport to be mapped * * The port must have been previously been registered using nmport_register. * * Note that if extmem is used (either via an option or by calling an * nmport_extmem_* function before nmport_register()), no new mmap() is issued. * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_mmap(struct nmport_d *); /* the following functions undo the actions of nmport_new(), nmport_parse(), * nmport_register() and nmport_mmap(), respectively. */ void nmport_delete(struct nmport_d *); void nmport_undo_parse(struct nmport_d *); void nmport_undo_register(struct nmport_d *); void nmport_undo_mmap(struct nmport_d *); /* nmport_prepare - create a port descriptor, but do not open it * @portspec the port opening specification * * This functions creates a new nmport_d and initializes it according to * @portspec. It is equivalent to nmport_new() followed by nmport_parse(). * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ struct nmport_d *nmport_prepare(const char *portspec); /* nmport_open_desc - open an initialized port descriptor * @d the descriptor we want to open * * Registers the port with netmap and maps the rings and buffers into the * process memory. It is equivalent to nmport_register() followed by * nmport_mmap(). * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_open_desc(struct nmport_d *d); /* the following functions undo the actions of nmport_prepare() * and nmport_open_desc(), respectively. */ void nmport_undo_prepare(struct nmport_d *); void nmport_undo_open_desc(struct nmport_d *); /* nmport_clone - copy an nmport_d * @d the nmport_d we want to copy * * Copying an nmport_d by hand should be avoided, since adjustments are needed * and some part of the state cannot be easily duplicated. This function * creates a copy of @d in a safe way. The returned nmport_d contains * nmreq_header and nmreq_register structures equivalent to those contained in * @d, except for the option list, which is ignored. The returned nmport_d is * already nmport_prepare()d, but it must still be nmport_open_desc()ed. The * new nmport_d uses the same nmctx as @d. * * If extmem was used for @d, then @d cannot be nmport_clone()d until it has * been nmport_register()ed. * * In case of error, the function returns NULL, sets errno to an error value * and sends an error message to the nmctx error() method. */ struct nmport_d *nmport_clone(struct nmport_d *); /* nmport_extmem - use extmem for this port * @d the port we want to use the extmem for * @base the base address of the extmem region * @size the size in bytes of the extmem region * * the memory that contains the netmap ifs, rings and buffers is usually * allocated by netmap and later mmap()ed by the applications. It is sometimes * useful to reverse this process, by having the applications allocate some * memory (through mmap() or otherwise) and then let netmap use it. The extmem * option can be used to implement this latter strategy. The option can be * passed through the portspec using the '@extmem:...' syntax, or * programmatically by calling nmport_extmem() or nmport_extmem_from_file() * between nmport_parse() and nmport_register() (or between nmport_prepare() * and nmport_open_desc()). * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_extmem(struct nmport_d *d, void *base, size_t size); /* nmport_extmem_from_file - use the extmem obtained by mapping a file * @d the port we want to use the extmem for * @fname path of the file we want to map * * This works like nmport_extmem, but the extmem memory is obtained by * mmap()ping @fname. nmport_close() will also automatically munmap() the file. * * It returns 0 on success. On failure it returns -1, sets errno to an error * value and sends an error message to the error() method of the context used * when @d was created. Moreover, *@d is left unchanged. */ int nmport_extmem_from_file(struct nmport_d *d, const char *fname); /* nmport_extmem_getinfo - opbtai a pointer to the extmem configuration * @d the port we want to obtain the pointer from * * Returns a pointer to the nmreq_pools_info structure containing the * configuration of the extmem attached to port @d, or NULL if no extmem * is attached. This can be used to set the desired configuration before * registering the port, or to read the actual configuration after * registration. */ struct nmreq_pools_info* nmport_extmem_getinfo(struct nmport_d *d); +/* nmport_offset - use offsets for this port + * @initial the initial offset for all the slots + * @maxoff the maximum offset + * @bits the number of bits of slot->ptr to use for the offsets + * @mingap the minimum gap betwen offsets (in shared buffers) + * + * With this option the lower @bits bits of the ptr field in the netmap_slot + * can be used to specify an offset into the buffer. All offsets will be set + * to the @initial value by netmap. + * + * The offset field can be read and updated using the bitmask found in + * ring->offset_mask after a successful register. netmap_user.h contains + * some helper macros (NETMAP_ROFFSET, NETMAP_WOFFSET and NETMAP_BUF_OFFSET). + * + * For RX rings, the user writes the offset o in an empty slot before passing + * it to netmap; then, netmap will write the incoming packet at an offset o' >= + * o in the buffer. o' may be larger than o because of, e.g., alignment + * constrains. If o' > o netmap will also update the offset field in the slot. + * Note that large offsets may cause the port to split the packet over several + * slots, setting the NS_MOREFRAG flag accordingly. + * + * For TX rings, the user may prepare the packet to send at an offset o into + * the buffer and write o in the offset field. Netmap will send the packets + * starting o bytes in the buffer. Note that the address of the packet must + * comply with any alignment constraints that the port may have, or the result + * will be undefined. The user may read the alignment constraint in the new + * ring->buf_align field. It is also possibile that empty slots already come + * with a non-zero offset o specified in the offset field. In this case, the + * user will have to write the packet at an offset o' >= o. + * + * The user must also declare the @maxoff offset that she is going to use. Any + * offset larger than this will be truncated. + * + * The user may also declare a @mingap (ignored if zero) if she plans to use + * offsets to share the same buffer among several slots. Netmap will guarantee + * that it will never write more than @mingap bytes for each slot, irrespective + * of the buffer length. + */ +int nmport_offset(struct nmport_d *d, uint64_t initial, uint64_t maxoff, + uint64_t bits, uint64_t mingap); + /* enable/disable options * * These functions can be used to disable options that the application cannot * or doesn't want to handle, or to enable options that require special support * from the application and are, therefore, disabled by default. Disabled * options will cause an error if encountered during option parsing. * * If the option is unknown, nmport_disable_option is a NOP, while * nmport_enable_option returns -1 and sets errno to EOPNOTSUPP. * * These functions are not threadsafe and are meant to be used at the beginning * of the program. */ void nmport_disable_option(const char *opt); int nmport_enable_option(const char *opt); /* nmreq manipulation * * nmreq_header_init - initialize an nmreq_header * @hdr the nmreq_header to initialize * @reqtype the kind of netmap request * @body the body of the request * * Initialize the nr_version, nr_reqtype and nr_body fields of *@hdr. * The other fields are set to zero. */ void nmreq_header_init(struct nmreq_header *hdr, uint16_t reqtype, void *body); /* * These functions allow for finer grained parsing of portspecs. They are used * internally by nmport_parse(). */ /* nmreq_header_decode - initialize an nmreq_header * @ppspec: (in/out) pointer to a pointer to the portspec * @hdr: pointer to the nmreq_header to be initialized * @ctx: pointer to the nmctx to use (for errors) * * This function fills the @hdr the nr_name field with the port name extracted * from *@pifname. The other fields of *@hdr are unchanged. The @pifname is * updated to point at the first char past the port name. * * Returns 0 on success. In case of error, -1 is returned with errno set to * EINVAL, @pifname is unchanged, *@hdr is also unchanged, and an error message * is sent through @ctx->error(). */ int nmreq_header_decode(const char **ppspec, struct nmreq_header *hdr, struct nmctx *ctx); /* nmreq_regiter_decode - initialize an nmreq_register * @pmode: (in/out) pointer to a pointer to an opening mode * @reg: pointer to the nmreq_register to be initialized * @ctx: pointer to the nmctx to use (for errors) * * This function fills the nr_mode, nr_ringid, nr_flags and nr_mem_id fields of * the structure pointed by @reg, according to the opening mode specified by * *@pmode. The other fields of *@reg are unchanged. The @pmode is updated to * point at the first char past the opening mode. * * If a '@' is encountered followed by something which is not a number, parsing * stops (without error) and @pmode is left pointing at the '@' char. The * nr_mode, nr_ringid and nr_flags fields are still updated, but nr_mem_id is * not touched and the interpretation of the '@' field is left to the caller. * * Returns 0 on success. In case of error, -1 is returned with errno set to * EINVAL, @pmode is unchanged, *@reg is also unchanged, and an error message * is sent through @ctx->error(). */ int nmreq_register_decode(const char **pmode, struct nmreq_register *reg, struct nmctx *ctx); /* nmreq_options_decode - parse the "options" part of the portspec * @opt: pointer to the option list * @parsers: list of option parsers * @token: token to pass to each parser * @ctx: pointer to the nmctx to use (for errors and malloc/free) * * This function parses each option in @opt. Each option is matched (based on * the "option" prefix) to a corresponding parser in @parsers. The function * checks that the syntax is appropriate for the parser and it assigns all the * keys mentioned in the option. It then passes control to the parser, to * interpret the keys values. * * Returns 0 on success. In case of error, -1 is returned, errno is set to an * error value and a message is sent to @ctx->error(). The effects of partially * interpreted options may not be undone. */ struct nmreq_opt_parser; int nmreq_options_decode(const char *opt, struct nmreq_opt_parser *parsers, void *token, struct nmctx *ctx); struct nmreq_parse_ctx; /* type of the option-parsers callbacks */ typedef int (*nmreq_opt_parser_cb)(struct nmreq_parse_ctx *); #define NMREQ_OPT_MAXKEYS 16 /* max nr of recognized keys per option */ /* struct nmreq_opt_key - describes an option key */ struct nmreq_opt_key { const char *key; /* the key name */ int id; /* its position in the parse context */ unsigned int flags; #define NMREQ_OPTK_ALLOWEMPTY (1U << 0) /* =value may be omitted */ #define NMREQ_OPTK_MUSTSET (1U << 1) /* the key is mandatory */ #define NMREQ_OPTK_DEFAULT (1U << 2) /* this is the default key */ }; /* struct nmreq_opt_parser - describes an option parser */ struct nmreq_opt_parser { const char *prefix; /* matches one option prefix */ nmreq_opt_parser_cb parse; /* the parse callback */ int default_key; /* which option is the default if the parser is multi-key (-1 if none) */ int nr_keys; unsigned int flags; #define NMREQ_OPTF_DISABLED (1U << 0) #define NMREQ_OPTF_ALLOWEMPTY (1U << 1) /* =value can be omitted */ struct nmreq_opt_parser *next; /* list of options */ /* recognized keys */ struct nmreq_opt_key keys[NMREQ_OPT_MAXKEYS]; } __attribute__((aligned(16))); /* struct nmreq_parse_ctx - the parse context received by the parse callback */ struct nmreq_parse_ctx { struct nmctx *ctx; /* the nmctx for errors and malloc/free */ void *token; /* the token passed to nmreq_options_parse */ /* the value (i.e., the part after the = sign) of each recognized key * is assigned to the corresponding entry in this array, based on the * key id. Unassigned keys are left at NULL. */ const char *keys[NMREQ_OPT_MAXKEYS]; }; /* nmreq_get_mem_id - get the mem_id of the given port * @portname pointer to a pointer to the portname * @ctx pointer to the nmctx to use (for errors) * * *@portname must point to a substem:vpname porname, possibly followed by * something else. * * If successful, returns the mem_id of *@portname and moves @portname past the * subsystem:vpname part of the input. In case of error it returns -1, sets * errno to an error value and sends an error message to ctx->error(). */ int32_t nmreq_get_mem_id(const char **portname, struct nmctx *ctx); /* option list manipulation */ void nmreq_push_option(struct nmreq_header *, struct nmreq_option *); void nmreq_remove_option(struct nmreq_header *, struct nmreq_option *); struct nmreq_option *nmreq_find_option(struct nmreq_header *, uint32_t); void nmreq_free_options(struct nmreq_header *); const char* nmreq_option_name(uint32_t); #define nmreq_foreach_option(h_, o_) \ for ((o_) = (struct nmreq_option *)((uintptr_t)((h_)->nr_options));\ (o_) != NULL;\ (o_) = (struct nmreq_option *)((uintptr_t)((o_)->nro_next))) /* nmctx manipulation */ /* the nmctx serves a few purposes: * * - maintain a list of all memory regions open by the program, so that two * ports that are using the same region (as identified by the mem_id) will * point to the same nmem_d instance. * * - allow the user to specify how to lock accesses to the above list, if * needed (lock() callback) * * - allow the user to specify how error messages should be delivered (error() * callback) * * - select the verbosity of the library (verbose field); if verbose==0, no * errors are sent to the error() callback * * - allow the user to override the malloc/free functions used by the library * (malloc() and free() callbacks) * */ typedef void (*nmctx_error_cb)(struct nmctx *, const char *); typedef void *(*nmctx_malloc_cb)(struct nmctx *,size_t); typedef void (*nmctx_free_cb)(struct nmctx *,void *); typedef void (*nmctx_lock_cb)(struct nmctx *, int); struct nmctx { int verbose; nmctx_error_cb error; nmctx_malloc_cb malloc; nmctx_free_cb free; nmctx_lock_cb lock; struct nmem_d *mem_descs; }; /* nmctx_get - obtain a pointer to the current default context */ struct nmctx *nmctx_get(void); /* nmctx_set_default - change the default context * @ctx pointer to the new context * * Returns a pointer to the previous default context. */ struct nmctx *nmctx_set_default(struct nmctx *ctx); /* internal functions and data structures */ /* struct nmem_d - describes a memory region currently used */ struct nmem_d { uint16_t mem_id; /* the region netmap identifier */ int refcount; /* how many nmport_d's point here */ void *mem; /* memory region base address */ size_t size; /* memory region size */ int is_extmem; /* was it obtained via extmem? */ /* pointers for the circular list implementation. * The list head is the mem_descs filed in the nmctx */ struct nmem_d *next; struct nmem_d *prev; }; /* a trick to force the inclusion of libpthread only if requested. If * LIBNETMAP_NOTHREADSAFE is defined, no pthread symbol is imported. * * There is no need to actually call this function: the ((used)) attribute is * sufficient to include it in the image. */ static __attribute__((used)) void libnetmap_init(void) { #ifndef LIBNETMAP_NOTHREADSAFE extern int nmctx_threadsafe; /* dummy assignment to link-in the nmctx-pthread.o object. The proper * inizialization is performed only once in the library constructor * defined there. */ nmctx_threadsafe = 1; #endif /* LIBNETMAP_NOTHREADSAFE */ } /* nmctx_set_threadsafe - install a threadsafe default context * * called by the constructor in nmctx-pthread.o to initialize a lock and install * the lock() callback in the default context. */ void nmctx_set_threadsafe(void); /* nmctx_ferror - format and send an error message */ void nmctx_ferror(struct nmctx *, const char *, ...); /* nmctx_malloc - allocate memory */ void *nmctx_malloc(struct nmctx *, size_t); /* nmctx_free - free memory allocated via nmctx_malloc */ void nmctx_free(struct nmctx *, void *); /* nmctx_lock - lock the list of nmem_d */ void nmctx_lock(struct nmctx *); /* nmctx_unlock - unlock the list of nmem_d */ void nmctx_unlock(struct nmctx *); #endif /* LIBNETMAP_H_ */ diff --git a/lib/libnetmap/nmctx.c b/lib/libnetmap/nmctx.c index 5f2c8c32febd..f5288e58fa9f 100644 --- a/lib/libnetmap/nmctx.c +++ b/lib/libnetmap/nmctx.c @@ -1,141 +1,142 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2018 Universita` di Pisa * All rights reserved. * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #define LIBNETMAP_NOTHREADSAFE #include "libnetmap.h" static void nmctx_default_error(struct nmctx *ctx, const char *errmsg) { + (void)ctx; fprintf(stderr, "%s\n", errmsg); } static void * nmctx_default_malloc(struct nmctx *ctx, size_t sz) { (void)ctx; return malloc(sz); } static void nmctx_default_free(struct nmctx *ctx, void *p) { (void)ctx; free(p); } static struct nmctx nmctx_global = { .verbose = 1, .error = nmctx_default_error, .malloc = nmctx_default_malloc, .free = nmctx_default_free, .lock = NULL, }; static struct nmctx *nmctx_default = &nmctx_global; struct nmctx * nmctx_get(void) { return nmctx_default; } struct nmctx * nmctx_set_default(struct nmctx *ctx) { struct nmctx *old = nmctx_default; nmctx_default = ctx; return old; } #define MAXERRMSG 1000 void nmctx_ferror(struct nmctx *ctx, const char *fmt, ...) { char errmsg[MAXERRMSG]; va_list ap; int rv; if (!ctx->verbose) return; va_start(ap, fmt); rv = vsnprintf(errmsg, MAXERRMSG, fmt, ap); va_end(ap); if (rv > 0) { if (rv < MAXERRMSG) { ctx->error(ctx, errmsg); } else { ctx->error(ctx, "error message too long"); } } else { ctx->error(ctx, "internal error"); } } void * nmctx_malloc(struct nmctx *ctx, size_t sz) { return ctx->malloc(ctx, sz); } void nmctx_free(struct nmctx *ctx, void *p) { ctx->free(ctx, p); } void nmctx_lock(struct nmctx *ctx) { if (ctx->lock != NULL) ctx->lock(ctx, 1); } void nmctx_unlock(struct nmctx *ctx) { if (ctx->lock != NULL) ctx->lock(ctx, 0); } diff --git a/lib/libnetmap/nmport.c b/lib/libnetmap/nmport.c index a3fd7e87100f..58267bd8e9b1 100644 --- a/lib/libnetmap/nmport.c +++ b/lib/libnetmap/nmport.c @@ -1,840 +1,914 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2018 Universita` di Pisa * All rights reserved. * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define LIBNETMAP_NOTHREADSAFE #include "libnetmap.h" struct nmport_cleanup_d { struct nmport_cleanup_d *next; void (*cleanup)(struct nmport_cleanup_d *, struct nmport_d *); }; static void nmport_push_cleanup(struct nmport_d *d, struct nmport_cleanup_d *c) { c->next = d->clist; d->clist = c; } static void nmport_pop_cleanup(struct nmport_d *d) { struct nmport_cleanup_d *top; top = d->clist; d->clist = d->clist->next; (*top->cleanup)(top, d); nmctx_free(d->ctx, top); } void nmport_do_cleanup(struct nmport_d *d) { while (d->clist != NULL) { nmport_pop_cleanup(d); } } static struct nmport_d * nmport_new_with_ctx(struct nmctx *ctx) { struct nmport_d *d; /* allocate a descriptor */ d = nmctx_malloc(ctx, sizeof(*d)); if (d == NULL) { nmctx_ferror(ctx, "cannot allocate nmport descriptor"); goto out; } memset(d, 0, sizeof(*d)); nmreq_header_init(&d->hdr, NETMAP_REQ_REGISTER, &d->reg); d->ctx = ctx; d->fd = -1; out: return d; } struct nmport_d * nmport_new(void) { struct nmctx *ctx = nmctx_get(); return nmport_new_with_ctx(ctx); } void nmport_delete(struct nmport_d *d) { nmctx_free(d->ctx, d); } void nmport_extmem_cleanup(struct nmport_cleanup_d *c, struct nmport_d *d) { (void)c; if (d->extmem == NULL) return; nmreq_remove_option(&d->hdr, &d->extmem->nro_opt); nmctx_free(d->ctx, d->extmem); d->extmem = NULL; } int nmport_extmem(struct nmport_d *d, void *base, size_t size) { struct nmctx *ctx = d->ctx; struct nmport_cleanup_d *clnup = NULL; if (d->register_done) { nmctx_ferror(ctx, "%s: cannot set extmem of an already registered port", d->hdr.nr_name); errno = EINVAL; return -1; } if (d->extmem != NULL) { nmctx_ferror(ctx, "%s: extmem already in use", d->hdr.nr_name); errno = EINVAL; return -1; } clnup = (struct nmport_cleanup_d *)nmctx_malloc(ctx, sizeof(*clnup)); if (clnup == NULL) { nmctx_ferror(ctx, "failed to allocate cleanup descriptor"); errno = ENOMEM; return -1; } d->extmem = nmctx_malloc(ctx, sizeof(*d->extmem)); if (d->extmem == NULL) { nmctx_ferror(ctx, "%s: cannot allocate extmem option", d->hdr.nr_name); nmctx_free(ctx, clnup); errno = ENOMEM; return -1; } memset(d->extmem, 0, sizeof(*d->extmem)); d->extmem->nro_usrptr = (uintptr_t)base; d->extmem->nro_opt.nro_reqtype = NETMAP_REQ_OPT_EXTMEM; d->extmem->nro_info.nr_memsize = size; nmreq_push_option(&d->hdr, &d->extmem->nro_opt); clnup->cleanup = nmport_extmem_cleanup; nmport_push_cleanup(d, clnup); return 0; } struct nmport_extmem_from_file_cleanup_d { struct nmport_cleanup_d up; void *p; size_t size; }; void nmport_extmem_from_file_cleanup(struct nmport_cleanup_d *c, struct nmport_d *d) { + (void)d; struct nmport_extmem_from_file_cleanup_d *cc = (struct nmport_extmem_from_file_cleanup_d *)c; munmap(cc->p, cc->size); } int nmport_extmem_from_file(struct nmport_d *d, const char *fname) { struct nmctx *ctx = d->ctx; int fd = -1; off_t mapsize; void *p; struct nmport_extmem_from_file_cleanup_d *clnup = NULL; clnup = nmctx_malloc(ctx, sizeof(*clnup)); if (clnup == NULL) { nmctx_ferror(ctx, "cannot allocate cleanup descriptor"); errno = ENOMEM; goto fail; } fd = open(fname, O_RDWR); if (fd < 0) { nmctx_ferror(ctx, "cannot open '%s': %s", fname, strerror(errno)); goto fail; } mapsize = lseek(fd, 0, SEEK_END); if (mapsize < 0) { nmctx_ferror(ctx, "failed to obtain filesize of '%s': %s", fname, strerror(errno)); goto fail; } p = mmap(0, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { nmctx_ferror(ctx, "cannot mmap '%s': %s", fname, strerror(errno)); goto fail; } close(fd); clnup->p = p; clnup->size = mapsize; clnup->up.cleanup = nmport_extmem_from_file_cleanup; nmport_push_cleanup(d, &clnup->up); if (nmport_extmem(d, p, mapsize) < 0) goto fail; return 0; fail: if (fd >= 0) close(fd); if (clnup != NULL) { if (clnup->p != MAP_FAILED) nmport_pop_cleanup(d); else nmctx_free(ctx, clnup); } return -1; } struct nmreq_pools_info* nmport_extmem_getinfo(struct nmport_d *d) { if (d->extmem == NULL) return NULL; return &d->extmem->nro_info; } +struct nmport_offset_cleanup_d { + struct nmport_cleanup_d up; + struct nmreq_opt_offsets *opt; +}; + +static void +nmport_offset_cleanup(struct nmport_cleanup_d *c, + struct nmport_d *d) +{ + struct nmport_offset_cleanup_d *cc = + (struct nmport_offset_cleanup_d *)c; + + nmreq_remove_option(&d->hdr, &cc->opt->nro_opt); + nmctx_free(d->ctx, cc->opt); +} + +int +nmport_offset(struct nmport_d *d, uint64_t initial, + uint64_t maxoff, uint64_t bits, uint64_t mingap) +{ + struct nmctx *ctx = d->ctx; + struct nmreq_opt_offsets *opt; + struct nmport_offset_cleanup_d *clnup = NULL; + + clnup = nmctx_malloc(ctx, sizeof(*clnup)); + if (clnup == NULL) { + nmctx_ferror(ctx, "cannot allocate cleanup descriptor"); + errno = ENOMEM; + return -1; + } + + opt = nmctx_malloc(ctx, sizeof(*opt)); + if (opt == NULL) { + nmctx_ferror(ctx, "%s: cannot allocate offset option", d->hdr.nr_name); + nmctx_free(ctx, clnup); + errno = ENOMEM; + return -1; + } + memset(opt, 0, sizeof(*opt)); + opt->nro_opt.nro_reqtype = NETMAP_REQ_OPT_OFFSETS; + opt->nro_offset_bits = bits; + opt->nro_initial_offset = initial; + opt->nro_max_offset = maxoff; + opt->nro_min_gap = mingap; + nmreq_push_option(&d->hdr, &opt->nro_opt); + + clnup->up.cleanup = nmport_offset_cleanup; + clnup->opt = opt; + nmport_push_cleanup(d, &clnup->up); + + return 0; +} + /* head of the list of options */ static struct nmreq_opt_parser *nmport_opt_parsers; #define NPOPT_PARSER(o) nmport_opt_##o##_parser #define NPOPT_DESC(o) nmport_opt_##o##_desc #define NPOPT_NRKEYS(o) (NPOPT_DESC(o).nr_keys) #define NPOPT_DECL(o, f) \ static int NPOPT_PARSER(o)(struct nmreq_parse_ctx *); \ static struct nmreq_opt_parser NPOPT_DESC(o) = { \ .prefix = #o, \ .parse = NPOPT_PARSER(o), \ .flags = (f), \ .default_key = -1, \ .nr_keys = 0, \ .next = NULL, \ }; \ static void __attribute__((constructor)) \ nmport_opt_##o##_ctor(void) \ { \ NPOPT_DESC(o).next = nmport_opt_parsers; \ nmport_opt_parsers = &NPOPT_DESC(o); \ } struct nmport_key_desc { struct nmreq_opt_parser *option; const char *key; unsigned int flags; int id; }; static void nmport_opt_key_ctor(struct nmport_key_desc *k) { struct nmreq_opt_parser *o = k->option; struct nmreq_opt_key *ok; k->id = o->nr_keys; ok = &o->keys[k->id]; ok->key = k->key; ok->id = k->id; ok->flags = k->flags; o->nr_keys++; if (ok->flags & NMREQ_OPTK_DEFAULT) o->default_key = ok->id; } #define NPKEY_DESC(o, k) nmport_opt_##o##_key_##k##_desc #define NPKEY_ID(o, k) (NPKEY_DESC(o, k).id) #define NPKEY_DECL(o, k, f) \ static struct nmport_key_desc NPKEY_DESC(o, k) = { \ .option = &NPOPT_DESC(o), \ .key = #k, \ .flags = (f), \ .id = -1, \ }; \ static void __attribute__((constructor)) \ nmport_opt_##o##_key_##k##_ctor(void) \ { \ nmport_opt_key_ctor(&NPKEY_DESC(o, k)); \ } #define nmport_key(p, o, k) ((p)->keys[NPKEY_ID(o, k)]) #define nmport_defkey(p, o) ((p)->keys[NPOPT_DESC(o).default_key]) NPOPT_DECL(share, 0) NPKEY_DECL(share, port, NMREQ_OPTK_DEFAULT|NMREQ_OPTK_MUSTSET) NPOPT_DECL(extmem, 0) NPKEY_DECL(extmem, file, NMREQ_OPTK_DEFAULT|NMREQ_OPTK_MUSTSET) NPKEY_DECL(extmem, if_num, 0) NPKEY_DECL(extmem, if_size, 0) NPKEY_DECL(extmem, ring_num, 0) NPKEY_DECL(extmem, ring_size, 0) NPKEY_DECL(extmem, buf_num, 0) NPKEY_DECL(extmem, buf_size, 0) NPOPT_DECL(conf, 0) NPKEY_DECL(conf, rings, 0) NPKEY_DECL(conf, host_rings, 0) NPKEY_DECL(conf, slots, 0) NPKEY_DECL(conf, tx_rings, 0) NPKEY_DECL(conf, rx_rings, 0) NPKEY_DECL(conf, host_tx_rings, 0) NPKEY_DECL(conf, host_rx_rings, 0) NPKEY_DECL(conf, tx_slots, 0) NPKEY_DECL(conf, rx_slots, 0) +NPOPT_DECL(offset, NMREQ_OPTF_DISABLED) + NPKEY_DECL(offset, initial, NMREQ_OPTK_DEFAULT|NMREQ_OPTK_MUSTSET) + NPKEY_DECL(offset, bits, 0) static int NPOPT_PARSER(share)(struct nmreq_parse_ctx *p) { struct nmctx *ctx = p->ctx; struct nmport_d *d = p->token; int32_t mem_id; const char *v = nmport_defkey(p, share); mem_id = nmreq_get_mem_id(&v, ctx); if (mem_id < 0) return -1; if (d->reg.nr_mem_id && d->reg.nr_mem_id != mem_id) { nmctx_ferror(ctx, "cannot set mem_id to %"PRId32", already set to %"PRIu16"", mem_id, d->reg.nr_mem_id); errno = EINVAL; return -1; } d->reg.nr_mem_id = mem_id; return 0; } static int NPOPT_PARSER(extmem)(struct nmreq_parse_ctx *p) { struct nmport_d *d; struct nmreq_pools_info *pi; int i; d = p->token; if (nmport_extmem_from_file(d, nmport_key(p, extmem, file)) < 0) return -1; pi = &d->extmem->nro_info; for (i = 0; i < NPOPT_NRKEYS(extmem); i++) { const char *k = p->keys[i]; uint32_t v; if (k == NULL) continue; v = atoi(k); if (i == NPKEY_ID(extmem, if_num)) { pi->nr_if_pool_objtotal = v; } else if (i == NPKEY_ID(extmem, if_size)) { pi->nr_if_pool_objsize = v; } else if (i == NPKEY_ID(extmem, ring_num)) { pi->nr_ring_pool_objtotal = v; } else if (i == NPKEY_ID(extmem, ring_size)) { pi->nr_ring_pool_objsize = v; } else if (i == NPKEY_ID(extmem, buf_num)) { pi->nr_buf_pool_objtotal = v; } else if (i == NPKEY_ID(extmem, buf_size)) { pi->nr_buf_pool_objsize = v; } } return 0; } static int NPOPT_PARSER(conf)(struct nmreq_parse_ctx *p) { struct nmport_d *d; d = p->token; if (nmport_key(p, conf, rings) != NULL) { uint16_t nr_rings = atoi(nmport_key(p, conf, rings)); d->reg.nr_tx_rings = nr_rings; d->reg.nr_rx_rings = nr_rings; } if (nmport_key(p, conf, host_rings) != NULL) { uint16_t nr_rings = atoi(nmport_key(p, conf, host_rings)); d->reg.nr_host_tx_rings = nr_rings; d->reg.nr_host_rx_rings = nr_rings; } if (nmport_key(p, conf, slots) != NULL) { uint32_t nr_slots = atoi(nmport_key(p, conf, slots)); d->reg.nr_tx_slots = nr_slots; d->reg.nr_rx_slots = nr_slots; } if (nmport_key(p, conf, tx_rings) != NULL) { d->reg.nr_tx_rings = atoi(nmport_key(p, conf, tx_rings)); } if (nmport_key(p, conf, rx_rings) != NULL) { d->reg.nr_rx_rings = atoi(nmport_key(p, conf, rx_rings)); } if (nmport_key(p, conf, host_tx_rings) != NULL) { d->reg.nr_host_tx_rings = atoi(nmport_key(p, conf, host_tx_rings)); } if (nmport_key(p, conf, host_rx_rings) != NULL) { d->reg.nr_host_rx_rings = atoi(nmport_key(p, conf, host_rx_rings)); } if (nmport_key(p, conf, tx_slots) != NULL) { d->reg.nr_tx_slots = atoi(nmport_key(p, conf, tx_slots)); } if (nmport_key(p, conf, rx_slots) != NULL) { d->reg.nr_rx_slots = atoi(nmport_key(p, conf, rx_slots)); } return 0; } +static int +NPOPT_PARSER(offset)(struct nmreq_parse_ctx *p) +{ + struct nmport_d *d; + uint64_t initial, bits; + + d = p->token; + + initial = atoi(nmport_key(p, offset, initial)); + bits = 0; + if (nmport_key(p, offset, bits) != NULL) + bits = atoi(nmport_key(p, offset, bits)); + + return nmport_offset(d, initial, initial, bits, 0); +} + + void nmport_disable_option(const char *opt) { struct nmreq_opt_parser *p; for (p = nmport_opt_parsers; p != NULL; p = p->next) { if (!strcmp(p->prefix, opt)) { p->flags |= NMREQ_OPTF_DISABLED; } } } int nmport_enable_option(const char *opt) { struct nmreq_opt_parser *p; for (p = nmport_opt_parsers; p != NULL; p = p->next) { if (!strcmp(p->prefix, opt)) { p->flags &= ~NMREQ_OPTF_DISABLED; return 0; } } errno = EOPNOTSUPP; return -1; } int nmport_parse(struct nmport_d *d, const char *ifname) { const char *scan = ifname; if (nmreq_header_decode(&scan, &d->hdr, d->ctx) < 0) { goto err; } /* parse the register request */ if (nmreq_register_decode(&scan, &d->reg, d->ctx) < 0) { goto err; } /* parse the options, if any */ if (nmreq_options_decode(scan, nmport_opt_parsers, d, d->ctx) < 0) { goto err; } return 0; err: nmport_undo_parse(d); return -1; } void nmport_undo_parse(struct nmport_d *d) { nmport_do_cleanup(d); memset(&d->reg, 0, sizeof(d->reg)); memset(&d->hdr, 0, sizeof(d->hdr)); } struct nmport_d * nmport_prepare(const char *ifname) { struct nmport_d *d; /* allocate a descriptor */ d = nmport_new(); if (d == NULL) goto err; /* parse the header */ if (nmport_parse(d, ifname) < 0) goto err; return d; err: nmport_undo_prepare(d); return NULL; } void nmport_undo_prepare(struct nmport_d *d) { if (d == NULL) return; nmport_undo_parse(d); nmport_delete(d); } int nmport_register(struct nmport_d *d) { struct nmctx *ctx = d->ctx; if (d->register_done) { errno = EINVAL; nmctx_ferror(ctx, "%s: already registered", d->hdr.nr_name); return -1; } d->fd = open("/dev/netmap", O_RDWR); if (d->fd < 0) { nmctx_ferror(ctx, "/dev/netmap: %s", strerror(errno)); goto err; } if (ioctl(d->fd, NIOCCTRL, &d->hdr) < 0) { struct nmreq_option *o; int option_errors = 0; nmreq_foreach_option(&d->hdr, o) { if (o->nro_status) { nmctx_ferror(ctx, "%s: option %s: %s", d->hdr.nr_name, nmreq_option_name(o->nro_reqtype), strerror(o->nro_status)); option_errors++; } } if (!option_errors) nmctx_ferror(ctx, "%s: %s", d->hdr.nr_name, strerror(errno)); goto err; } d->register_done = 1; return 0; err: nmport_undo_register(d); return -1; } void nmport_undo_register(struct nmport_d *d) { if (d->fd >= 0) close(d->fd); d->fd = -1; d->register_done = 0; } /* lookup the mem_id in the mem-list: do a new mmap() if * not found, reuse existing otherwise */ int nmport_mmap(struct nmport_d *d) { struct nmctx *ctx = d->ctx; struct nmem_d *m = NULL; u_int num_tx, num_rx; - int i; + unsigned int i; if (d->mmap_done) { errno = EINVAL; nmctx_ferror(ctx, "%s: already mapped", d->hdr.nr_name); return -1; } if (!d->register_done) { errno = EINVAL; nmctx_ferror(ctx, "cannot map unregistered port"); return -1; } nmctx_lock(ctx); for (m = ctx->mem_descs; m != NULL; m = m->next) if (m->mem_id == d->reg.nr_mem_id) break; if (m == NULL) { m = nmctx_malloc(ctx, sizeof(*m)); if (m == NULL) { nmctx_ferror(ctx, "cannot allocate memory descriptor"); goto err; } memset(m, 0, sizeof(*m)); if (d->extmem != NULL) { m->mem = (void *)((uintptr_t)d->extmem->nro_usrptr); m->size = d->extmem->nro_info.nr_memsize; m->is_extmem = 1; } else { m->mem = mmap(NULL, d->reg.nr_memsize, PROT_READ|PROT_WRITE, MAP_SHARED, d->fd, 0); if (m->mem == MAP_FAILED) { nmctx_ferror(ctx, "mmap: %s", strerror(errno)); goto err; } m->size = d->reg.nr_memsize; } m->mem_id = d->reg.nr_mem_id; m->next = ctx->mem_descs; if (ctx->mem_descs != NULL) ctx->mem_descs->prev = m; ctx->mem_descs = m; } m->refcount++; nmctx_unlock(ctx); d->mem = m; d->nifp = NETMAP_IF(m->mem, d->reg.nr_offset); num_tx = d->reg.nr_tx_rings + d->nifp->ni_host_tx_rings; for (i = 0; i < num_tx && !d->nifp->ring_ofs[i]; i++) ; - d->first_tx_ring = i; + d->cur_tx_ring = d->first_tx_ring = i; for ( ; i < num_tx && d->nifp->ring_ofs[i]; i++) ; d->last_tx_ring = i - 1; num_rx = d->reg.nr_rx_rings + d->nifp->ni_host_rx_rings; for (i = 0; i < num_rx && !d->nifp->ring_ofs[i + num_tx]; i++) ; - d->first_rx_ring = i; + d->cur_rx_ring = d->first_rx_ring = i; for ( ; i < num_rx && d->nifp->ring_ofs[i + num_tx]; i++) ; d->last_rx_ring = i - 1; d->mmap_done = 1; return 0; err: nmctx_unlock(ctx); nmport_undo_mmap(d); return -1; } void nmport_undo_mmap(struct nmport_d *d) { struct nmem_d *m; struct nmctx *ctx = d->ctx; m = d->mem; if (m == NULL) return; nmctx_lock(ctx); m->refcount--; if (m->refcount <= 0) { if (!m->is_extmem && m->mem != MAP_FAILED) munmap(m->mem, m->size); /* extract from the list and free */ if (m->next != NULL) m->next->prev = m->prev; if (m->prev != NULL) m->prev->next = m->next; else ctx->mem_descs = m->next; nmctx_free(ctx, m); d->mem = NULL; } nmctx_unlock(ctx); d->mmap_done = 0; d->mem = NULL; d->nifp = NULL; d->first_tx_ring = 0; d->last_tx_ring = 0; d->first_rx_ring = 0; d->last_rx_ring = 0; d->cur_tx_ring = 0; d->cur_rx_ring = 0; } int nmport_open_desc(struct nmport_d *d) { if (nmport_register(d) < 0) goto err; if (nmport_mmap(d) < 0) goto err; return 0; err: nmport_undo_open_desc(d); return -1; } void nmport_undo_open_desc(struct nmport_d *d) { nmport_undo_mmap(d); nmport_undo_register(d); } struct nmport_d * nmport_open(const char *ifname) { struct nmport_d *d; /* prepare the descriptor */ d = nmport_prepare(ifname); if (d == NULL) goto err; /* open netmap and register */ if (nmport_open_desc(d) < 0) goto err; return d; err: nmport_close(d); return NULL; } void nmport_close(struct nmport_d *d) { if (d == NULL) return; nmport_undo_open_desc(d); nmport_undo_prepare(d); } struct nmport_d * nmport_clone(struct nmport_d *d) { struct nmport_d *c; struct nmctx *ctx; ctx = d->ctx; if (d->extmem != NULL && !d->register_done) { errno = EINVAL; nmctx_ferror(ctx, "cannot clone unregistered port that is using extmem"); return NULL; } c = nmport_new_with_ctx(ctx); if (c == NULL) return NULL; /* copy the output of parse */ c->hdr = d->hdr; /* redirect the pointer to the body */ c->hdr.nr_body = (uintptr_t)&c->reg; /* options are not cloned */ c->hdr.nr_options = 0; c->reg = d->reg; /* this also copies the mem_id */ /* put the new port in an un-registered, unmapped state */ c->fd = -1; c->nifp = NULL; c->register_done = 0; c->mem = NULL; c->extmem = NULL; c->mmap_done = 0; c->first_tx_ring = 0; c->last_tx_ring = 0; c->first_rx_ring = 0; c->last_rx_ring = 0; c->cur_tx_ring = 0; c->cur_rx_ring = 0; return c; } int nmport_inject(struct nmport_d *d, const void *buf, size_t size) { u_int c, n = d->last_tx_ring - d->first_tx_ring + 1, ri = d->cur_tx_ring; for (c = 0; c < n ; c++, ri++) { /* compute current ring to use */ struct netmap_ring *ring; uint32_t i, j, idx; size_t rem; if (ri > d->last_tx_ring) ri = d->first_tx_ring; ring = NETMAP_TXRING(d->nifp, ri); rem = size; j = ring->cur; while (rem > ring->nr_buf_size && j != ring->tail) { rem -= ring->nr_buf_size; j = nm_ring_next(ring, j); } if (j == ring->tail && rem > 0) continue; i = ring->cur; while (i != j) { idx = ring->slot[i].buf_idx; ring->slot[i].len = ring->nr_buf_size; ring->slot[i].flags = NS_MOREFRAG; nm_pkt_copy(buf, NETMAP_BUF(ring, idx), ring->nr_buf_size); i = nm_ring_next(ring, i); buf = (char *)buf + ring->nr_buf_size; } idx = ring->slot[i].buf_idx; ring->slot[i].len = rem; ring->slot[i].flags = 0; nm_pkt_copy(buf, NETMAP_BUF(ring, idx), rem); ring->head = ring->cur = nm_ring_next(ring, i); d->cur_tx_ring = ri; return size; } return 0; /* fail */ } diff --git a/lib/libnetmap/nmreq.c b/lib/libnetmap/nmreq.c index 7f4b2703d22d..39ee53c818c5 100644 --- a/lib/libnetmap/nmreq.c +++ b/lib/libnetmap/nmreq.c @@ -1,719 +1,716 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2018 Universita` di Pisa * All rights reserved. * * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include //#define NMREQ_DEBUG #ifdef NMREQ_DEBUG #define NETMAP_WITH_LIBS #define ED(...) D(__VA_ARGS__) #else #define ED(...) /* an identifier is a possibly empty sequence of alphanum characters and * underscores */ static int nm_is_identifier(const char *s, const char *e) { for (; s != e; s++) { if (!isalnum(*s) && *s != '_') { return 0; } } return 1; } #endif /* NMREQ_DEBUG */ #include #define LIBNETMAP_NOTHREADSAFE #include "libnetmap.h" void nmreq_push_option(struct nmreq_header *h, struct nmreq_option *o) { o->nro_next = h->nr_options; h->nr_options = (uintptr_t)o; } struct nmreq_prefix { const char *prefix; /* the constant part of the prefix */ size_t len; /* its strlen() */ uint32_t flags; #define NR_P_ID (1U << 0) /* whether an identifier is needed */ #define NR_P_SKIP (1U << 1) /* whether the scope must be passed to netmap */ #define NR_P_EMPTYID (1U << 2) /* whether an empty identifier is allowed */ }; #define declprefix(prefix, flags) { (prefix), (sizeof(prefix) - 1), (flags) } static struct nmreq_prefix nmreq_prefixes[] = { declprefix("netmap", NR_P_SKIP), declprefix(NM_BDG_NAME, NR_P_ID|NR_P_EMPTYID), { NULL } /* terminate the list */ }; void nmreq_header_init(struct nmreq_header *h, uint16_t reqtype, void *body) { memset(h, 0, sizeof(*h)); h->nr_version = NETMAP_API; h->nr_reqtype = reqtype; h->nr_body = (uintptr_t)body; } int nmreq_header_decode(const char **pifname, struct nmreq_header *h, struct nmctx *ctx) { const char *scan = NULL; const char *vpname = NULL; const char *pipesep = NULL; u_int namelen; const char *ifname = *pifname; struct nmreq_prefix *p; scan = ifname; for (p = nmreq_prefixes; p->prefix != NULL; p++) { if (!strncmp(scan, p->prefix, p->len)) break; } if (p->prefix == NULL) { nmctx_ferror(ctx, "%s: invalid request, prefix unknown or missing", *pifname); goto fail; } scan += p->len; vpname = index(scan, ':'); if (vpname == NULL) { nmctx_ferror(ctx, "%s: missing ':'", ifname); goto fail; } if (vpname != scan) { /* there is an identifier, can we accept it? */ if (!(p->flags & NR_P_ID)) { nmctx_ferror(ctx, "%s: no identifier allowed between '%s' and ':'", *pifname, p->prefix); goto fail; } if (!nm_is_identifier(scan, vpname)) { nmctx_ferror(ctx, "%s: invalid identifier '%.*s'", *pifname, vpname - scan, scan); goto fail; } } else { if ((p->flags & NR_P_ID) && !(p->flags & NR_P_EMPTYID)) { nmctx_ferror(ctx, "%s: identifier is missing between '%s' and ':'", *pifname, p->prefix); goto fail; } } ++vpname; /* skip the colon */ if (p->flags & NR_P_SKIP) ifname = vpname; scan = vpname; /* scan for a separator */ for (; *scan && !index("-*^/@", *scan); scan++) ; /* search for possible pipe indicators */ for (pipesep = vpname; pipesep != scan && !index("{}", *pipesep); pipesep++) ; if (!nm_is_identifier(vpname, pipesep)) { nmctx_ferror(ctx, "%s: invalid port name '%.*s'", *pifname, pipesep - vpname, vpname); goto fail; } if (pipesep != scan) { pipesep++; if (*pipesep == '\0') { nmctx_ferror(ctx, "%s: invalid empty pipe name", *pifname); goto fail; } if (!nm_is_identifier(pipesep, scan)) { nmctx_ferror(ctx, "%s: invalid pipe name '%.*s'", *pifname, scan - pipesep, pipesep); goto fail; } } namelen = scan - ifname; if (namelen >= sizeof(h->nr_name)) { nmctx_ferror(ctx, "name '%.*s' too long", namelen, ifname); goto fail; } if (namelen == 0) { nmctx_ferror(ctx, "%s: invalid empty port name", *pifname); goto fail; } /* fill the header */ memcpy(h->nr_name, ifname, namelen); h->nr_name[namelen] = '\0'; ED("name %s", h->nr_name); *pifname = scan; return 0; fail: errno = EINVAL; return -1; } /* * 0 not recognized * -1 error * >= 0 mem_id */ int32_t nmreq_get_mem_id(const char **pifname, struct nmctx *ctx) { int fd = -1; struct nmreq_header gh; struct nmreq_port_info_get gb; const char *ifname; errno = 0; ifname = *pifname; if (ifname == NULL) goto fail; /* try to look for a netmap port with this name */ fd = open("/dev/netmap", O_RDWR); if (fd < 0) { nmctx_ferror(ctx, "cannot open /dev/netmap: %s", strerror(errno)); goto fail; } nmreq_header_init(&gh, NETMAP_REQ_PORT_INFO_GET, &gb); if (nmreq_header_decode(&ifname, &gh, ctx) < 0) { goto fail; } memset(&gb, 0, sizeof(gb)); if (ioctl(fd, NIOCCTRL, &gh) < 0) { nmctx_ferror(ctx, "cannot get info for '%s': %s", *pifname, strerror(errno)); goto fail; } *pifname = ifname; close(fd); return gb.nr_mem_id; fail: if (fd >= 0) close(fd); if (!errno) errno = EINVAL; return -1; } int nmreq_register_decode(const char **pifname, struct nmreq_register *r, struct nmctx *ctx) { enum { P_START, P_RNGSFXOK, P_GETNUM, P_FLAGS, P_FLAGSOK, P_MEMID, P_ONESW } p_state; long num; const char *scan = *pifname; uint32_t nr_mode; uint16_t nr_mem_id; uint16_t nr_ringid; uint64_t nr_flags; /* fill the request */ p_state = P_START; /* defaults */ nr_mode = NR_REG_ALL_NIC; /* default for no suffix */ nr_mem_id = r->nr_mem_id; /* if non-zero, further updates are disabled */ nr_ringid = 0; nr_flags = 0; while (*scan) { switch (p_state) { case P_START: switch (*scan) { case '^': /* only SW ring */ nr_mode = NR_REG_SW; p_state = P_ONESW; break; case '*': /* NIC and SW */ nr_mode = NR_REG_NIC_SW; p_state = P_RNGSFXOK; break; case '-': /* one NIC ring pair */ nr_mode = NR_REG_ONE_NIC; p_state = P_GETNUM; break; case '/': /* start of flags */ p_state = P_FLAGS; break; case '@': /* start of memid */ p_state = P_MEMID; break; default: nmctx_ferror(ctx, "unknown modifier: '%c'", *scan); goto fail; } scan++; break; case P_RNGSFXOK: switch (*scan) { case '/': p_state = P_FLAGS; break; case '@': p_state = P_MEMID; break; default: nmctx_ferror(ctx, "unexpected character: '%c'", *scan); goto fail; } scan++; break; case P_GETNUM: if (!isdigit(*scan)) { nmctx_ferror(ctx, "got '%s' while expecting a number", scan); goto fail; } num = strtol(scan, (char **)&scan, 10); if (num < 0 || num >= NETMAP_RING_MASK) { nmctx_ferror(ctx, "'%ld' out of range [0, %d)", num, NETMAP_RING_MASK); goto fail; } nr_ringid = num & NETMAP_RING_MASK; p_state = P_RNGSFXOK; break; case P_FLAGS: case P_FLAGSOK: switch (*scan) { case '@': p_state = P_MEMID; scan++; continue; case 'x': nr_flags |= NR_EXCLUSIVE; break; case 'z': nr_flags |= NR_ZCOPY_MON; break; case 't': nr_flags |= NR_MONITOR_TX; break; case 'r': nr_flags |= NR_MONITOR_RX; break; case 'R': nr_flags |= NR_RX_RINGS_ONLY; break; case 'T': nr_flags |= NR_TX_RINGS_ONLY; break; default: nmctx_ferror(ctx, "unrecognized flag: '%c'", *scan); goto fail; } scan++; p_state = P_FLAGSOK; break; case P_MEMID: if (!isdigit(*scan)) { scan--; /* escape to options */ goto out; } num = strtol(scan, (char **)&scan, 10); if (num <= 0) { nmctx_ferror(ctx, "invalid mem_id: '%ld'", num); goto fail; } if (nr_mem_id && nr_mem_id != num) { nmctx_ferror(ctx, "invalid setting of mem_id to %ld (already set to %"PRIu16")", num, nr_mem_id); goto fail; } nr_mem_id = num; p_state = P_RNGSFXOK; break; case P_ONESW: if (!isdigit(*scan)) { p_state = P_RNGSFXOK; } else { nr_mode = NR_REG_ONE_SW; p_state = P_GETNUM; } break; } } if (p_state == P_MEMID && !*scan) { nmctx_ferror(ctx, "invalid empty mem_id"); goto fail; } if (p_state != P_START && p_state != P_RNGSFXOK && p_state != P_FLAGSOK && p_state != P_MEMID && p_state != P_ONESW) { nmctx_ferror(ctx, "unexpected end of request"); goto fail; } out: ED("flags: %s %s %s %s %s %s", (nr_flags & NR_EXCLUSIVE) ? "EXCLUSIVE" : "", (nr_flags & NR_ZCOPY_MON) ? "ZCOPY_MON" : "", (nr_flags & NR_MONITOR_TX) ? "MONITOR_TX" : "", (nr_flags & NR_MONITOR_RX) ? "MONITOR_RX" : "", (nr_flags & NR_RX_RINGS_ONLY) ? "RX_RINGS_ONLY" : "", (nr_flags & NR_TX_RINGS_ONLY) ? "TX_RINGS_ONLY" : ""); r->nr_mode = nr_mode; r->nr_ringid = nr_ringid; r->nr_flags = nr_flags; r->nr_mem_id = nr_mem_id; *pifname = scan; return 0; fail: if (!errno) errno = EINVAL; return -1; } static int nmreq_option_parsekeys(const char *prefix, char *body, struct nmreq_opt_parser *p, struct nmreq_parse_ctx *pctx) { char *scan; char delim1; struct nmreq_opt_key *k; scan = body; delim1 = *scan; while (delim1 != '\0') { char *key, *value; char delim; size_t vlen; key = scan; for ( scan++; *scan != '\0' && *scan != '=' && *scan != ','; scan++) { if (*scan == '-') *scan = '_'; } delim = *scan; *scan = '\0'; scan++; for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL; k++) { if (!strcmp(k->key, key)) goto found; } nmctx_ferror(pctx->ctx, "unknown key: '%s'", key); errno = EINVAL; return -1; found: if (pctx->keys[k->id] != NULL) { nmctx_ferror(pctx->ctx, "option '%s': duplicate key '%s', already set to '%s'", prefix, key, pctx->keys[k->id]); errno = EINVAL; return -1; } value = scan; for ( ; *scan != '\0' && *scan != ','; scan++) ; delim1 = *scan; *scan = '\0'; vlen = scan - value; scan++; if (delim == '=') { pctx->keys[k->id] = (vlen ? value : NULL); } else { if (!(k->flags & NMREQ_OPTK_ALLOWEMPTY)) { nmctx_ferror(pctx->ctx, "option '%s': missing '=value' for key '%s'", prefix, key); errno = EINVAL; return -1; } pctx->keys[k->id] = key; } } /* now check that all no-default keys have been assigned */ for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL; k++) { if ((k->flags & NMREQ_OPTK_MUSTSET) && pctx->keys[k->id] == NULL) { nmctx_ferror(pctx->ctx, "option '%s': mandatory key '%s' not assigned", prefix, k->key); errno = EINVAL; return -1; } } return 0; } static int nmreq_option_decode1(char *opt, struct nmreq_opt_parser *parsers, void *token, struct nmctx *ctx) { struct nmreq_opt_parser *p; const char *prefix; char *scan; char delim; struct nmreq_parse_ctx pctx; int i; prefix = opt; /* find the delimiter */ for (scan = opt; *scan != '\0' && *scan != ':' && *scan != '='; scan++) ; delim = *scan; *scan = '\0'; scan++; /* find the prefix */ for (p = parsers; p != NULL; p = p->next) { if (!strcmp(prefix, p->prefix)) break; } if (p == NULL) { nmctx_ferror(ctx, "unknown option: '%s'", prefix); errno = EINVAL; return -1; } if (p->flags & NMREQ_OPTF_DISABLED) { nmctx_ferror(ctx, "option '%s' is not supported", prefix); errno = EOPNOTSUPP; return -1; } /* prepare the parse context */ pctx.ctx = ctx; pctx.token = token; for (i = 0; i < NMREQ_OPT_MAXKEYS; i++) pctx.keys[i] = NULL; switch (delim) { case '\0': /* no body */ if (!(p->flags & NMREQ_OPTF_ALLOWEMPTY)) { nmctx_ferror(ctx, "syntax error: missing body after '%s'", prefix); errno = EINVAL; return -1; } break; case '=': /* the body goes to the default option key, if any */ if (p->default_key < 0 || p->default_key >= NMREQ_OPT_MAXKEYS) { nmctx_ferror(ctx, "syntax error: '=' not valid after '%s'", prefix); errno = EINVAL; return -1; } if (*scan == '\0') { nmctx_ferror(ctx, "missing value for option '%s'", prefix); errno = EINVAL; return -1; } pctx.keys[p->default_key] = scan; break; case ':': /* parse 'key=value' strings */ if (nmreq_option_parsekeys(prefix, scan, p, &pctx) < 0) return -1; break; } return p->parse(&pctx); } int nmreq_options_decode(const char *opt, struct nmreq_opt_parser parsers[], void *token, struct nmctx *ctx) { const char *scan, *opt1; char *w; size_t len; int ret; if (*opt == '\0') return 0; /* empty list, OK */ if (*opt != '@') { nmctx_ferror(ctx, "option list does not start with '@'"); errno = EINVAL; return -1; } scan = opt; do { scan++; /* skip the plus */ opt1 = scan; /* start of option */ /* find the end of the option */ for ( ; *scan != '\0' && *scan != '@'; scan++) ; len = scan - opt1; if (len == 0) { nmctx_ferror(ctx, "invalid empty option"); errno = EINVAL; return -1; } w = nmctx_malloc(ctx, len + 1); if (w == NULL) { nmctx_ferror(ctx, "out of memory"); errno = ENOMEM; return -1; } memcpy(w, opt1, len); w[len] = '\0'; ret = nmreq_option_decode1(w, parsers, token, ctx); nmctx_free(ctx, w); if (ret < 0) return -1; } while (*scan != '\0'); return 0; } struct nmreq_option * nmreq_find_option(struct nmreq_header *h, uint32_t t) { - struct nmreq_option *o = NULL; + struct nmreq_option *o; - nmreq_foreach_option(h, o) { + for (o = (struct nmreq_option *)h->nr_options; o != NULL; + o = (struct nmreq_option *)o->nro_next) { if (o->nro_reqtype == t) break; } return o; } void nmreq_remove_option(struct nmreq_header *h, struct nmreq_option *o) { - struct nmreq_option **nmo; + struct nmreq_option **nmo; for (nmo = (struct nmreq_option **)&h->nr_options; *nmo != NULL; - nmo = (struct nmreq_option **)&(*nmo)->nro_next) { + nmo = (struct nmreq_option **)&(*nmo)->nro_next) { if (*nmo == o) { *((uint64_t *)(*nmo)) = o->nro_next; o->nro_next = (uint64_t)(uintptr_t)NULL; break; } } } void nmreq_free_options(struct nmreq_header *h) { struct nmreq_option *o, *next; - /* - * Note: can't use nmreq_foreach_option() here; it frees the - * list as it's walking and nmreq_foreach_option() isn't - * modification-safe. - */ - for (o = (struct nmreq_option *)(uintptr_t)h->nr_options; o != NULL; - o = next) { - next = (struct nmreq_option *)(uintptr_t)o->nro_next; + for (o = (struct nmreq_option *)h->nr_options; o != NULL; o = next) { + next = (struct nmreq_option *)o->nro_next; free(o); } } const char* nmreq_option_name(uint32_t nro_reqtype) { switch (nro_reqtype) { case NETMAP_REQ_OPT_EXTMEM: return "extmem"; case NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS: return "sync-kloop-eventfds"; case NETMAP_REQ_OPT_CSB: return "csb"; case NETMAP_REQ_OPT_SYNC_KLOOP_MODE: return "sync-kloop-mode"; + case NETMAP_REQ_OPT_OFFSETS: + return "offsets"; default: return "unknown"; } } #if 0 #include static void nmreq_dump(struct nmport_d *d) { printf("header:\n"); printf(" nr_version: %"PRIu16"\n", d->hdr.nr_version); printf(" nr_reqtype: %"PRIu16"\n", d->hdr.nr_reqtype); printf(" nr_reserved: %"PRIu32"\n", d->hdr.nr_reserved); printf(" nr_name: %s\n", d->hdr.nr_name); printf(" nr_options: %lx\n", (unsigned long)d->hdr.nr_options); printf(" nr_body: %lx\n", (unsigned long)d->hdr.nr_body); printf("\n"); printf("register (%p):\n", (void *)d->hdr.nr_body); printf(" nr_mem_id: %"PRIu16"\n", d->reg.nr_mem_id); printf(" nr_ringid: %"PRIu16"\n", d->reg.nr_ringid); printf(" nr_mode: %lx\n", (unsigned long)d->reg.nr_mode); printf(" nr_flags: %lx\n", (unsigned long)d->reg.nr_flags); printf("\n"); if (d->hdr.nr_options) { struct nmreq_opt_extmem *e = (struct nmreq_opt_extmem *)d->hdr.nr_options; printf("opt_extmem (%p):\n", e); printf(" nro_opt.nro_next: %lx\n", (unsigned long)e->nro_opt.nro_next); printf(" nro_opt.nro_reqtype: %"PRIu32"\n", e->nro_opt.nro_reqtype); printf(" nro_usrptr: %lx\n", (unsigned long)e->nro_usrptr); printf(" nro_info.nr_memsize %"PRIu64"\n", e->nro_info.nr_memsize); } printf("\n"); printf("mem (%p):\n", d->mem); printf(" refcount: %d\n", d->mem->refcount); printf(" mem: %p\n", d->mem->mem); printf(" size: %zu\n", d->mem->size); printf("\n"); printf("rings:\n"); printf(" tx: [%d, %d]\n", d->first_tx_ring, d->last_tx_ring); printf(" rx: [%d, %d]\n", d->first_rx_ring, d->last_rx_ring); } int main(int argc, char *argv[]) { struct nmport_d *d; if (argc < 2) { fprintf(stderr, "usage: %s netmap-expr\n", argv[0]); return 1; } d = nmport_open(argv[1]); if (d != NULL) { nmreq_dump(d); nmport_close(d); } return 0; } #endif