aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorVincenzo Maffione <vmaffione@FreeBSD.org>2020-08-28 20:03:54 +0000
committerVincenzo Maffione <vmaffione@FreeBSD.org>2020-08-28 20:03:54 +0000
commit5c4f8d801ca081e38ca3bfd9d36bfcd55329d703 (patch)
treef2a7c9bc34c99fe6d803c5ca8cba2f66a05ea6f5 /lib
parent609de97e0490003fcb6a5288c1112a058940a210 (diff)
downloadsrc-5c4f8d801ca081e38ca3bfd9d36bfcd55329d703.tar.gz
src-5c4f8d801ca081e38ca3bfd9d36bfcd55329d703.zip
Notes
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile1
-rw-r--r--lib/libnetmap/Makefile16
-rw-r--r--lib/libnetmap/libnetmap.h660
-rw-r--r--lib/libnetmap/nmctx-pthreads.c47
-rw-r--r--lib/libnetmap/nmctx.c111
-rw-r--r--lib/libnetmap/nmport.c810
-rw-r--r--lib/libnetmap/nmreq.c684
7 files changed, 2329 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile
index d196ae4c3687..ff3e36a7136e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -71,6 +71,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
libmt \
lib80211 \
libnetbsd \
+ libnetmap \
libnv \
libopenbsd \
libopie \
diff --git a/lib/libnetmap/Makefile b/lib/libnetmap/Makefile
new file mode 100644
index 000000000000..4229180147d3
--- /dev/null
+++ b/lib/libnetmap/Makefile
@@ -0,0 +1,16 @@
+#
+# $FreeBSD$
+#
+
+.include <src.opts.mk>
+
+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 <bsd.lib.mk>
diff --git a/lib/libnetmap/libnetmap.h b/lib/libnetmap/libnetmap.h
new file mode 100644
index 000000000000..6736ee9cc46b
--- /dev/null
+++ b/lib/libnetmap/libnetmap.h
@@ -0,0 +1,660 @@
+/*-
+ * 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 <net/netmap_user.h>
+
+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_ */
diff --git a/lib/libnetmap/nmctx-pthreads.c b/lib/libnetmap/nmctx-pthreads.c
new file mode 100644
index 000000000000..8aa9322369c1
--- /dev/null
+++ b/lib/libnetmap/nmctx-pthreads.c
@@ -0,0 +1,47 @@
+/* $FreeBSD$ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <net/netmap_user.h>
+#include <pthread.h>
+#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;
diff --git a/lib/libnetmap/nmctx.c b/lib/libnetmap/nmctx.c
new file mode 100644
index 000000000000..244c0349670b
--- /dev/null
+++ b/lib/libnetmap/nmctx.c
@@ -0,0 +1,111 @@
+/* $FreeBSD$ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <net/netmap_user.h>
+#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);
+}
diff --git a/lib/libnetmap/nmport.c b/lib/libnetmap/nmport.c
new file mode 100644
index 000000000000..c22aafae85f5
--- /dev/null
+++ b/lib/libnetmap/nmport.c
@@ -0,0 +1,810 @@
+/* $FreeBSD$ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <net/netmap_user.h>
+#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 */
+}
diff --git a/lib/libnetmap/nmreq.c b/lib/libnetmap/nmreq.c
new file mode 100644
index 000000000000..8654a02214d6
--- /dev/null
+++ b/lib/libnetmap/nmreq.c
@@ -0,0 +1,684 @@
+/* $FreeBSD$ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+//#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 <net/netmap_user.h>
+#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)
+{
+ struct nmreq_option **nmo;
+
+ for (nmo = (struct nmreq_option **)&h->nr_options; *nmo != NULL;
+ 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;
+
+ 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 <inttypes.h>
+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