Index: lib/libnetmap/Makefile =================================================================== --- lib/libnetmap/Makefile +++ lib/libnetmap/Makefile @@ -0,0 +1,16 @@ +# +# $FreeBSD $ +# + +.include + +PACKAGE= lib${LIB} +LIB= netmap +SRCS= nmctx.c nmport.c \ + nmctx-pthreads.c nmreq.c +INCS= libnetmap.h +#MAN= libnetmap.3 +CFLAGS+= -I${SRCTOP}/sys/net -I${.CURDIR} +WARNS?= 2 + +.include Index: lib/libnetmap/libnetmap.h =================================================================== --- lib/libnetmap/libnetmap.h +++ lib/libnetmap/libnetmap.h @@ -0,0 +1,659 @@ +/*- + * 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. + */ + +#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. + * + */ + + +/* 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); + + +/* 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 *)((h_)->nr_options);\ + (o_) != NULL;\ + (o_) = (struct nmreq_option *)((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_ */ Index: lib/libnetmap/nmctx-pthreads.c =================================================================== --- lib/libnetmap/nmctx-pthreads.c +++ lib/libnetmap/nmctx-pthreads.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libnetmap.h" + +struct nmctx_pthread { + struct nmctx up; + pthread_mutex_t mutex; +}; + +static struct nmctx_pthread nmctx_pthreadsafe; + +static void +nmctx_pthread_lock(struct nmctx *ctx, int lock) +{ + struct nmctx_pthread *ctxp = + (struct nmctx_pthread *)ctx; + if (lock) { + pthread_mutex_lock(&ctxp->mutex); + } else { + pthread_mutex_unlock(&ctxp->mutex); + } +} + +void __attribute__ ((constructor)) +nmctx_set_threadsafe(void) +{ + struct nmctx *old; + + pthread_mutex_init(&nmctx_pthreadsafe.mutex, NULL); + old = nmctx_set_default(&nmctx_pthreadsafe.up); + nmctx_pthreadsafe.up = *old; + nmctx_pthreadsafe.up.lock = nmctx_pthread_lock; +} + +int nmctx_threadsafe; Index: lib/libnetmap/nmctx.c =================================================================== --- lib/libnetmap/nmctx.c +++ lib/libnetmap/nmctx.c @@ -0,0 +1,110 @@ +#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) +{ + 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); +} Index: lib/libnetmap/nmport.c =================================================================== --- lib/libnetmap/nmport.c +++ lib/libnetmap/nmport.c @@ -0,0 +1,809 @@ +#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) +{ + 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; +} + +/* 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) + + +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; +} + +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; + + 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 *)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; + 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; + 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 */ +} Index: lib/libnetmap/nmreq.c =================================================================== --- lib/libnetmap/nmreq.c +++ lib/libnetmap/nmreq.c @@ -0,0 +1,683 @@ +#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; + + 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) +{ + uintptr_t *scan; + + for (scan = &h->nr_options; *scan; + scan = &((struct nmreq_option *)*scan)->nro_next) { + if (*scan == (uintptr_t)o) { + *scan = o->nro_next; + o->nro_next = 0; + break; + } + } +} + +void +nmreq_free_options(struct nmreq_header *h) +{ + struct nmreq_option *o, *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"; + 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 Index: share/mk/bsd.libnames.mk =================================================================== --- share/mk/bsd.libnames.mk +++ share/mk/bsd.libnames.mk @@ -107,6 +107,7 @@ LIBNCURSES?= ${LIBDESTDIR}${LIBDIR_BASE}/libncurses.a LIBNCURSESW?= ${LIBDESTDIR}${LIBDIR_BASE}/libncursesw.a LIBNETGRAPH?= ${LIBDESTDIR}${LIBDIR_BASE}/libnetgraph.a +LIBNETMAP?= ${LIBDESTDIR}${LIBDIR_BASE}/libnetmap.a LIBNGATM?= ${LIBDESTDIR}${LIBDIR_BASE}/libngatm.a LIBNV?= ${LIBDESTDIR}${LIBDIR_BASE}/libnv.a LIBNVPAIR?= ${LIBDESTDIR}${LIBDIR_BASE}/libnvpair.a Index: share/mk/src.libnames.mk =================================================================== --- share/mk/src.libnames.mk +++ share/mk/src.libnames.mk @@ -146,6 +146,7 @@ ncurses \ ncursesw \ netgraph \ + netmap \ ngatm \ nv \ nvpair \ @@ -380,6 +381,7 @@ _DP_zfs_core= nvpair _DP_zpool= md pthread z nvpair avl umem _DP_be= zfs nvpair +_DP_netmap= # OFED support .if ${MK_OFED} != "no"