diff options
author | Ciprian Barbu <ciprian.barbu@linaro.org> | 2014-02-11 14:34:29 +0100 |
---|---|---|
committer | Maxim Uvarov <maxim.uvarov@linaro.org> | 2014-02-12 14:46:10 +0400 |
commit | 240c87f9f39df823bdbafbc3e893888318ad5096 (patch) | |
tree | 71a53cd8463ae04b84a274ace7def479528a61cb /platform | |
parent | 1e3914dffbcd9026fa278eb100ac2cb90ad15779 (diff) |
Netmap support
Signed-off-by: Ciprian Barbu <ciprian.barbu@linaro.org>
Diffstat (limited to 'platform')
-rw-r--r-- | platform/linux-generic/Makefile | 6 | ||||
-rw-r--r-- | platform/linux-generic/include/api/odp_pktio_netmap.h | 22 | ||||
-rw-r--r-- | platform/linux-generic/include/api/odp_pktio_types.h | 14 | ||||
-rw-r--r-- | platform/linux-generic/include/odp_packet_io_internal.h | 6 | ||||
-rw-r--r-- | platform/linux-generic/include/odp_packet_netmap.h | 68 | ||||
-rw-r--r-- | platform/linux-generic/source/odp_packet_io.c | 42 | ||||
-rw-r--r-- | platform/linux-generic/source/odp_packet_netmap.c | 385 |
7 files changed, 543 insertions, 0 deletions
diff --git a/platform/linux-generic/Makefile b/platform/linux-generic/Makefile index 080458603..282a474ca 100644 --- a/platform/linux-generic/Makefile +++ b/platform/linux-generic/Makefile @@ -33,6 +33,9 @@ LIB_DIR = ./lib DOC_DIR = ./doc CFLAGS += -I./include -I./include/api +ifeq ($(ODP_HAVE_NETMAP),yes) +CFLAGS += -DODP_HAVE_NETMAP +endif include $(ODP_ROOT)/Makefile.inc STATIC_LIB = $(ODP_LIB)/lib/libodp.a @@ -60,6 +63,9 @@ OBJS += $(OBJ_DIR)/odp_ticketlock.o OBJS += $(OBJ_DIR)/odp_time.o OBJS += $(OBJ_DIR)/odp_ring.o OBJS += $(OBJ_DIR)/odp_rwlock.o +ifeq ($(ODP_HAVE_NETMAP),yes) +OBJS += $(OBJ_DIR)/odp_packet_netmap.o +endif DEPS = $(OBJS:.o=.d) diff --git a/platform/linux-generic/include/api/odp_pktio_netmap.h b/platform/linux-generic/include/api/odp_pktio_netmap.h new file mode 100644 index 000000000..d57e2128f --- /dev/null +++ b/platform/linux-generic/include/api/odp_pktio_netmap.h @@ -0,0 +1,22 @@ + +/* Copyright (c) 2013, Linaro Limited + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef ODP_PKTIO_NETMAP_H +#define ODP_PKTIO_NETMAP_H + +#include <odp_pktio_types.h> + +#define ODP_NETMAP_MODE_HW 0 +#define ODP_NETMAP_MODE_SW 1 + +typedef struct { + odp_pktio_type_t type; + int netmap_mode; + uint16_t ringid; +} netmap_params_t; + +#endif diff --git a/platform/linux-generic/include/api/odp_pktio_types.h b/platform/linux-generic/include/api/odp_pktio_types.h index 1fd0bc05c..e6b4cbf5e 100644 --- a/platform/linux-generic/include/api/odp_pktio_types.h +++ b/platform/linux-generic/include/api/odp_pktio_types.h @@ -11,15 +11,29 @@ extern "C" { #endif +/* We should ensure that future enum values will never overlap, otherwise + * applications that want netmap suport might get in trouble if the odp lib + * was not built with netmap support and there are more types define below + */ + typedef enum { ODP_PKTIO_TYPE_SOCKET = 0x01, +#ifdef ODP_HAVE_NETMAP + ODP_PKTIO_TYPE_NETMAP = 0x02, +#endif } odp_pktio_type_t; #include <odp_pktio_socket.h> +#ifdef ODP_HAVE_NETMAP +#include <odp_pktio_netmap.h> +#endif typedef union odp_pktio_params_t { odp_pktio_type_t type; socket_params_t sock_params; +#ifdef ODP_HAVE_NETMAP + netmap_params_t nm_params; +#endif } odp_pktio_params_t; #ifdef __cplusplus diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h index fc00282fa..ba1ee9b7d 100644 --- a/platform/linux-generic/include/odp_packet_io_internal.h +++ b/platform/linux-generic/include/odp_packet_io_internal.h @@ -20,6 +20,9 @@ extern "C" { #include <odp_spinlock.h> #include <odp_packet_socket.h> +#ifdef ODP_HAVE_NETMAP +#include <odp_packet_netmap.h> +#endif struct pktio_entry { odp_spinlock_t lock; /**< entry spinlock */ @@ -28,6 +31,9 @@ struct pktio_entry { odp_queue_t outq_default; /**< default out queue */ odp_pktio_params_t params; /**< pktio parameters */ pkt_sock_t pkt_sock; /**< using socket API for IO */ +#ifdef ODP_HAVE_NETMAP + pkt_netmap_t pkt_nm; /**< using netmap API for IO */ +#endif }; typedef union { diff --git a/platform/linux-generic/include/odp_packet_netmap.h b/platform/linux-generic/include/odp_packet_netmap.h new file mode 100644 index 000000000..960ccbfa4 --- /dev/null +++ b/platform/linux-generic/include/odp_packet_netmap.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2013, Linaro Limited + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef ODP_PACKET_NETMAP_H +#define ODP_PACKET_NETMAP_H + +#include <stdint.h> + +#include <net/if.h> +#include <net/netmap.h> +#include <net/netmap_user.h> + +#include <odp_align.h> +#include <odp_debug.h> +#include <odp_buffer_pool.h> +#include <odp_packet.h> + +#include <odp_pktio_netmap.h> + +#define ODP_NETMAP_MODE_HW 0 +#define ODP_NETMAP_MODE_SW 1 + +#define NETMAP_BLOCKING_IO + +/** Packet socket using netmap mmaped rings for both Rx and Tx */ +typedef struct { + odp_buffer_pool_t pool; + size_t max_frame_len; /**< max frame len = buf_size - sizeof(pkt_hdr) */ + size_t l2_offset; /**< l2 hdr start offset from start of pkt payload */ + size_t buf_size; /**< size of buffer payload in 'pool' */ + struct nm_desc_t *nm_desc; + struct netmap_ring *rxring; + struct netmap_ring *txring; + + /********************************/ + odp_queue_t tx_access; /* Used for exclusive access to send packets */ + uint32_t if_flags; + uint32_t if_reqcap; + uint32_t if_curcap; + char ifname[32]; +} pkt_netmap_t; + +/** + * Configure an interface to work in netmap mode + */ +int setup_pkt_netmap(pkt_netmap_t * const pkt_nm, char *netdev, + odp_buffer_pool_t pool, netmap_params_t *nm_params); + +/** + * Switch interface from netmap mode to normal mode + */ +int close_pkt_netmap(pkt_netmap_t * const pkt_nm); + +/** + * Receive packets using netmap + */ +int recv_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[], + unsigned len); + +/** + * Send packets using netmap + */ +int send_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[], + unsigned len); +#endif diff --git a/platform/linux-generic/source/odp_packet_io.c b/platform/linux-generic/source/odp_packet_io.c index b3551df0a..08d3cbe38 100644 --- a/platform/linux-generic/source/odp_packet_io.c +++ b/platform/linux-generic/source/odp_packet_io.c @@ -13,6 +13,9 @@ #include <odp_spinlock.h> #include <odp_shared_memory.h> #include <odp_packet_socket.h> +#ifdef ODP_HAVE_NETMAP +#include <odp_packet_netmap.h> +#endif #include <odp_hints.h> #include <odp_config.h> #include <odp_queue_internal.h> @@ -20,6 +23,9 @@ #include <odp_debug.h> #include <odp_pktio_socket.h> +#ifdef ODP_HAVE_NETMAP +#include <odp_pktio_netmap.h> +#endif #include <string.h> @@ -114,6 +120,11 @@ static void init_pktio_entry(pktio_entry_t *entry, odp_pktio_params_t *params) case ODP_PKTIO_TYPE_SOCKET: memset(&entry->s.pkt_sock, 0, sizeof(entry->s.pkt_sock)); break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + memset(&entry->s.pkt_nm, 0, sizeof(entry->s.pkt_nm)); + break; +#endif } /* Save pktio parameters, type is the most useful */ memcpy(&entry->s.params, params, sizeof(*params)); @@ -169,6 +180,11 @@ odp_pktio_t odp_pktio_open(char *dev, odp_buffer_pool_t pool, case ODP_PKTIO_TYPE_SOCKET: ODP_DBG("Allocating socket pktio\n"); break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + ODP_DBG("Allocating netmap pktio\n"); + break; +#endif default: ODP_ERR("Invalid pktio type: %02x\n", params->type); return ODP_PKTIO_INVALID; @@ -192,6 +208,17 @@ odp_pktio_t odp_pktio_open(char *dev, odp_buffer_pool_t pool, id = ODP_PKTIO_INVALID; } break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + res = setup_pkt_netmap(&pktio_entry->s.pkt_nm, dev, + pool, ¶ms->nm_params); + if (res == -1) { + close_pkt_netmap(&pktio_entry->s.pkt_nm); + free_pktio_entry(id); + id = ODP_PKTIO_INVALID; + } + break; +#endif } unlock_entry(pktio_entry); @@ -213,6 +240,11 @@ int odp_pktio_close(odp_pktio_t id) case ODP_PKTIO_TYPE_SOCKET: res = close_pkt_sock(&entry->s.pkt_sock); break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + res = close_pkt_netmap(&entry->s.pkt_nm); + break; +#endif default: break; res |= free_pktio_entry(id); @@ -250,6 +282,11 @@ int odp_pktio_recv(odp_pktio_t id, odp_packet_t pkt_table[], unsigned len) case ODP_PKTIO_TYPE_SOCKET: pkts = recv_pkt_sock(&pktio_entry->s.pkt_sock, pkt_table, len); break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + pkts = recv_pkt_netmap(&pktio_entry->s.pkt_nm, pkt_table, len); + break; +#endif default: pkts = -1; break; @@ -278,6 +315,11 @@ int odp_pktio_send(odp_pktio_t id, odp_packet_t pkt_table[], unsigned len) case ODP_PKTIO_TYPE_SOCKET: pkts = send_pkt_sock(&pktio_entry->s.pkt_sock, pkt_table, len); break; +#ifdef ODP_HAVE_NETMAP + case ODP_PKTIO_TYPE_NETMAP: + pkts = send_pkt_netmap(&pktio_entry->s.pkt_nm, pkt_table, len); + break; +#endif default: pkts = -1; } diff --git a/platform/linux-generic/source/odp_packet_netmap.c b/platform/linux-generic/source/odp_packet_netmap.c new file mode 100644 index 000000000..1cbd84c1f --- /dev/null +++ b/platform/linux-generic/source/odp_packet_netmap.c @@ -0,0 +1,385 @@ +/* Copyright (c) 2013, Linaro Limited + * Copyright (c) 2013, Nokia Solutions and Networks + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * NETMAP I/O code inspired by the pkt-gen example application in netmap by: + * Copyright (C) 2011-2014 Matteo Landi, Luigi Rizzo. All rights reserved. + * Copyright (C) 2013-2014 Universita` di Pisa. All rights reserved. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> + +#include <linux/ethtool.h> +#include <linux/sockios.h> + +#include <odp_packet_internal.h> +#include <odp_hints.h> +#include <odp_thread.h> + +#include <helper/odp_eth.h> +#include <helper/odp_ip.h> + +#define NETMAP_WITH_LIBS +#include <odp_packet_netmap.h> + +/** Eth buffer start offset from u32-aligned address to make sure the following + * header (e.g. IP) starts at a 32-bit aligned address. + */ +#define ETHBUF_OFFSET (ODP_ALIGN_ROUNDUP(ODP_ETHHDR_LEN, sizeof(uint32_t)) \ + - ODP_ETHHDR_LEN) + +/** Round up buffer address to get a properly aliged eth buffer, i.e. aligned + * so that the next header always starts at a 32bit aligned address. + */ +#define ETHBUF_ALIGN(buf_ptr) ((uint8_t *)ODP_ALIGN_ROUNDUP_PTR((buf_ptr), \ + sizeof(uint32_t)) + ETHBUF_OFFSET) + +static int nm_do_ioctl(pkt_netmap_t * const pkt_nm, unsigned long cmd, + int subcmd) +{ + struct ethtool_value eval; + struct ifreq ifr; + int error; + int fd; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + ODP_ERR("Error: cannot get device control socket\n"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, pkt_nm->ifname, sizeof(ifr.ifr_name)); + + switch (cmd) { + case SIOCSIFFLAGS: + ifr.ifr_flags = pkt_nm->if_flags & 0xffff; + break; + case SIOCETHTOOL: + eval.cmd = subcmd; + eval.data = 0; + ifr.ifr_data = (caddr_t)&eval; + break; + default: + break; + } + error = ioctl(fd, cmd, &ifr); + if (error) + goto done; + + switch (cmd) { + case SIOCGIFFLAGS: + pkt_nm->if_flags = (ifr.ifr_flags << 16) | + (0xffff & ifr.ifr_flags); + ODP_DBG("flags are 0x%x\n", pkt_nm->if_flags); + break; + default: + break; + } +done: + close(fd); + if (error) + ODP_ERR("ioctl err %d %lu: %s\n", error, cmd, strerror(errno)); + + return error; +} + +int setup_pkt_netmap(pkt_netmap_t * const pkt_nm, char *netdev, + odp_buffer_pool_t pool, netmap_params_t *nm_params) +{ + char qname[ODP_QUEUE_NAME_LEN]; + char ifname[32]; + odp_packet_t pkt; + odp_buffer_t buf; + odp_buffer_t token; + uint8_t *pkt_buf; + int wait_link = 2; + uint16_t ringid; + int promisc = 1; /* TODO: maybe this should be exported to the user */ + uint8_t *l2_hdr; + int ret; + + if (pool == ODP_BUFFER_POOL_INVALID) + return -1; + pkt_nm->pool = pool; + + buf = odp_buffer_alloc(pool); + if (!odp_buffer_is_valid(buf)) + return -1; + + pkt = odp_packet_from_buffer(buf); + pkt_buf = odp_packet_buf_addr(pkt); + l2_hdr = ETHBUF_ALIGN(pkt_buf); + /* Store eth buffer offset for buffers from this pool */ + pkt_nm->l2_offset = (uintptr_t)l2_hdr - (uintptr_t)pkt_buf; + /* pkt buffer size */ + pkt_nm->buf_size = odp_buffer_size(buf); + /* max frame len taking into account the l2-offset */ + pkt_nm->max_frame_len = pkt_nm->buf_size - pkt_nm->l2_offset; + + odp_buffer_free(buf); + + if (nm_params->netmap_mode == ODP_NETMAP_MODE_SW) + ringid = NETMAP_SW_RING; + else + ringid = nm_params->ringid; + + strncpy(pkt_nm->ifname, netdev, sizeof(pkt_nm->ifname)); + snprintf(ifname, sizeof(ifname), "netmap:%s", netdev); + pkt_nm->nm_desc = nm_open(ifname, NULL, ringid, 0); + + if (pkt_nm->nm_desc == NULL) { + ODP_ERR("Error opening nm interface: %s\n", strerror(errno)); + return -1; + } + + ODP_DBG("thread %d mode %s mmap addr %p\n", + odp_thread_id(), + nm_params->netmap_mode == ODP_NETMAP_MODE_SW ? "SW" : "HW", + pkt_nm->nm_desc->mem); + + if (nm_params->netmap_mode == ODP_NETMAP_MODE_SW) { + pkt_nm->rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp, + pkt_nm->nm_desc->req.nr_rx_rings); + pkt_nm->txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp, + pkt_nm->nm_desc->req.nr_tx_rings); + } else { + pkt_nm->rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp, 0); + pkt_nm->txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp, 0); + } + + /* Set TX checksumming if hardware rings */ + if (nm_params->netmap_mode == ODP_NETMAP_MODE_HW) { + ret = nm_do_ioctl(pkt_nm, SIOCGIFFLAGS, 0); + if (ret) + return ret; + if ((pkt_nm->if_flags & IFF_UP) == 0) { + ODP_DBG("%s is down, bringing up...\n", pkt_nm->ifname); + pkt_nm->if_flags |= IFF_UP; + } + if (promisc) { + pkt_nm->if_flags |= IFF_PROMISC; + nm_do_ioctl(pkt_nm, SIOCSIFFLAGS, 0); + } + ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_SGSO); + if (ret) + ODP_DBG("ETHTOOL_SGSO not supported\n"); + + ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_STSO); + if (ret) + ODP_DBG("ETHTOOL_STSO not supported\n"); + /* Not sure why this is needed but the netmap example bridge + * uses it; it is possible that this causes some problem at + * startup, should be investigated further */ + /* + nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_SRXCSUM); + */ + ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_STXCSUM); + if (ret) + ODP_DBG("ETHTOOL_STXCSUM not supported\n"); + } + + /* Set up the TX access queue */ + snprintf(qname, sizeof(qname), "%s:%s-pktio_tx_access", netdev, + nm_params->netmap_mode == ODP_NETMAP_MODE_SW ? "SW" : "HW"); + pkt_nm->tx_access = odp_queue_create(qname, ODP_QUEUE_TYPE_POLL, NULL); + if (pkt_nm->tx_access == ODP_QUEUE_INVALID) { + ODP_ERR("Error: pktio queue creation failed\n"); + return -1; + } + token = odp_buffer_alloc(pool); + if (!odp_buffer_is_valid(token)) { + ODP_ERR("Error: token creation failed\n"); + return -1; + } + + odp_queue_enq(pkt_nm->tx_access, token); + + ODP_DBG("Wait for link to come up\n"); + sleep(wait_link); + ODP_DBG("Done\n"); + + return 0; +} + +int close_pkt_netmap(pkt_netmap_t * const pkt_nm) +{ + if (pkt_nm->nm_desc != NULL) { + nm_close(pkt_nm->nm_desc); + pkt_nm->nm_desc = NULL; + } + + return 0; +} + +int recv_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[], + unsigned len) +{ + struct netmap_ring *rxring; + int fd; + unsigned nb_rx = 0; + uint32_t limit, rx, cur; + odp_packet_t pkt = ODP_PACKET_INVALID; + +#ifdef NETMAP_BLOCKING_IO + struct pollfd fds[2]; + int ret; +#endif + + fd = pkt_nm->nm_desc->fd; +#ifdef NETMAP_BLOCKING_IO + fds[0].fd = fd; + fds[0].events = POLLIN; +#endif + + rxring = pkt_nm->rxring; + while (nb_rx < len) { + odp_buffer_t buf; + +#ifdef NETMAP_BLOCKING_IO + ret = poll(&fds[0], 1, 50); + if (ret <= 0 || (fds[0].revents & POLLERR)) + break; +#else + ioctl(fd, NIOCRXSYNC, NULL); +#endif + + if (nm_ring_empty(rxring)) { + /* No data on the wire, return to scheduler */ + break; + } + + limit = len - nb_rx; + if (nm_ring_space(rxring) < limit) + limit = nm_ring_space(rxring); + + ODP_DBG("receiving %d frames out of %u\n", limit, len); + + cur = rxring->cur; + for (rx = 0; rx < limit; rx++) { + struct netmap_slot *rslot; + char *p; + uint16_t payload_len; + uint8_t *pkt_buf; + uint8_t *l2_hdr; + + if (odp_likely(pkt == ODP_PACKET_INVALID)) { + buf = odp_buffer_alloc(pkt_nm->pool); + pkt = odp_packet_from_buffer(buf); + if (odp_unlikely(pkt == ODP_PACKET_INVALID)) + break; + } + + cur = rxring->cur; + rslot = &rxring->slot[cur]; + p = NETMAP_BUF(rxring, rslot->buf_idx); + payload_len = rslot->len; + + rxring->head = nm_ring_next(rxring, cur); + rxring->cur = nm_ring_next(rxring, cur); + + if (payload_len > pkt_nm->max_frame_len) { + ODP_ERR("Data partially lost %u %lu!\n", + payload_len, pkt_nm->max_frame_len); + payload_len = pkt_nm->max_frame_len; + } + + pkt_buf = odp_packet_buf_addr(pkt); + l2_hdr = pkt_buf + pkt_nm->l2_offset; + + /* For now copy the data in the mbuf, + worry about zero-copy later */ + memcpy(l2_hdr, p, payload_len); + + /* Initialize, parse and set packet header data */ + odp_packet_init(pkt); + odp_packet_parse(pkt, payload_len, pkt_nm->l2_offset); + + pkt_table[nb_rx] = pkt; + pkt = ODP_PACKET_INVALID; + nb_rx++; + } + + if (odp_unlikely(pkt == ODP_PACKET_INVALID)) + break; + } + + if (odp_unlikely(pkt != ODP_PACKET_INVALID)) + odp_buffer_free((odp_buffer_t) pkt); + + if (nb_rx) + ODP_DBG("<=== rcvd %03u frames from netmap adapter\n", nb_rx); + + return nb_rx; +} + +int send_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[], + unsigned len) +{ + int fd; + uint32_t i; + uint32_t limit; + void *txbuf; + struct netmap_ring *txring; + struct netmap_slot *slot; + odp_packet_t pkt; + odp_buffer_t token; + + fd = pkt_nm->nm_desc->fd; + + txring = pkt_nm->txring; + limit = nm_ring_space(txring); + if (len < limit) + limit = len; + + ODP_DBG("Sending %d packets out of %d to netmap %p %u\n", + limit, len, txring, txring->cur); + token = odp_queue_deq(pkt_nm->tx_access); + + for (i = 0; i < limit; i++) { + size_t frame_len; + uint32_t cur; + uint8_t *frame; + + cur = txring->cur; + slot = &txring->slot[cur]; + txbuf = NETMAP_BUF(txring, slot->buf_idx); + + pkt = pkt_table[i]; + frame = odp_packet_l2(pkt); + frame_len = odp_packet_get_len(pkt); + + memcpy(txbuf, frame, frame_len); + slot->len = frame_len; + txring->head = nm_ring_next(txring, cur); + txring->cur = nm_ring_next(txring, cur); + } + + odp_queue_enq(pkt_nm->tx_access, token); + + /* The netmap examples don't use this anymore, don't know why ... */ + /* ioctl(fd, NIOCTXSYNC, NULL); */ + (void)fd; + if (limit) + ODP_DBG("===> sent %03u frames to netmap adapter\n", limit); + + for (i = 0; i < len; i++) + odp_buffer_free(pkt_table[i]); + + return limit; +} |