lib: add libnetmap
This changeset introduces the new libnetmap library for writing netmap applications. Before libnetmap, applications could either use the kernel API directly (e.g. NIOCREGIF/NIOCCTRL) or the simple header-only-library netmap_user.h (e.g. nm_open(), nm_close(), nm_mmap() etc.) The new library offers more functionalities than netmap_user.h: - Support for complex netmap options, such as external memory allocators or per-buffer offsets. This opens the way to future extensions. - More flexibility in the netmap port bind options, such as non-numeric names for pipes, or the ability to specify the netmap allocator that must be used for a given port. - Automatic tracking of the netmap memory regions in use across the open ports. At the moment there is no man page, but the libnetmap.h header file has in-depth documentation. Reviewed by: hrs MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D26171
This commit is contained in:
parent
f0b210ea85
commit
cc60dbae46
@ -71,6 +71,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
|
||||
libmt \
|
||||
lib80211 \
|
||||
libnetbsd \
|
||||
libnetmap \
|
||||
libnv \
|
||||
libopenbsd \
|
||||
libopie \
|
||||
|
16
lib/libnetmap/Makefile
Normal file
16
lib/libnetmap/Makefile
Normal file
@ -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>
|
660
lib/libnetmap/libnetmap.h
Normal file
660
lib/libnetmap/libnetmap.h
Normal file
@ -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_ */
|
47
lib/libnetmap/nmctx-pthreads.c
Normal file
47
lib/libnetmap/nmctx-pthreads.c
Normal file
@ -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;
|
111
lib/libnetmap/nmctx.c
Normal file
111
lib/libnetmap/nmctx.c
Normal file
@ -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);
|
||||
}
|
810
lib/libnetmap/nmport.c
Normal file
810
lib/libnetmap/nmport.c
Normal file
@ -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 */
|
||||
}
|
684
lib/libnetmap/nmreq.c
Normal file
684
lib/libnetmap/nmreq.c
Normal file
@ -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
|
@ -108,6 +108,7 @@ LIBMT?= ${LIBDESTDIR}${LIBDIR_BASE}/libmt.a
|
||||
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
|
||||
|
@ -147,6 +147,7 @@ _LIBRARIES= \
|
||||
ncurses \
|
||||
ncursesw \
|
||||
netgraph \
|
||||
netmap \
|
||||
ngatm \
|
||||
nv \
|
||||
nvpair \
|
||||
@ -388,6 +389,7 @@ _DP_zfs_core= nvpair
|
||||
_DP_zpool= md pthread z icp spl nvpair avl umem
|
||||
_DP_zutil= avl tpool
|
||||
_DP_be= zfs spl nvpair
|
||||
_DP_netmap=
|
||||
|
||||
# OFED support
|
||||
.if ${MK_OFED} != "no"
|
||||
|
Loading…
Reference in New Issue
Block a user