[ovs-dev] [PATCH RFC] WIP: netdev-tpacket: Add AF_PACKET v3 support.

William Tu u9012063 at gmail.com
Fri Dec 20 00:41:25 UTC 2019


Currently the performance of sending packets from userspace
ovs to kernel veth device is pretty bad as reported from YiYang[1].
The patch adds AF_PACKET v3, tpacket v3, as another way to
tx/rx packet to linux device, hopefully showing better performance.

AF_PACKET v3 should get closed to 1Mpps, as shown[2]. However,
my current patch using iperf tcp shows only 1.4Gbps, maybe
I'm doing something wrong.  Also DPDK has similar implementation
using AF_PACKET v2[3].  This is still work-in-progress but any
feedbacks are welcome.

[1] https://patchwork.ozlabs.org/patch/1204939/
[2] slide 18, https://www.netdevconf.info/2.2/slides/karlsson-afpacket-talk.pdf
[3] dpdk/drivers/net/af_packet/rte_eth_af_packet.c
---
 lib/automake.mk            |   2 +
 lib/netdev-linux-private.h |  23 +++
 lib/netdev-linux.c         |  24 ++-
 lib/netdev-provider.h      |   1 +
 lib/netdev-tpacket.c       | 487 +++++++++++++++++++++++++++++++++++++++++++++
 lib/netdev-tpacket.h       |  43 ++++
 lib/netdev.c               |   1 +
 7 files changed, 580 insertions(+), 1 deletion(-)
 create mode 100644 lib/netdev-tpacket.c
 create mode 100644 lib/netdev-tpacket.h

diff --git a/lib/automake.mk b/lib/automake.mk
index 17b36b43d9d7..0c635404cb43 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -398,6 +398,8 @@ lib_libopenvswitch_la_SOURCES += \
 	lib/netdev-linux.c \
 	lib/netdev-linux.h \
 	lib/netdev-linux-private.h \
+	lib/netdev-tpacket.c \
+	lib/netdev-tpacket.h \
 	lib/netdev-offload-tc.c \
 	lib/netlink-conntrack.c \
 	lib/netlink-conntrack.h \
diff --git a/lib/netdev-linux-private.h b/lib/netdev-linux-private.h
index f08159aa7b53..99a2c03bb2a6 100644
--- a/lib/netdev-linux-private.h
+++ b/lib/netdev-linux-private.h
@@ -20,6 +20,7 @@
 #include <linux/filter.h>
 #include <linux/gen_stats.h>
 #include <linux/if_ether.h>
+#include <linux/if_packet.h>
 #include <linux/if_tun.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
@@ -37,6 +38,24 @@
 
 struct netdev;
 
+/* tpacket rx and tx ring structure. */
+struct tp_ring {
+    struct iovec *rd;   /* rd[n] points to mmap area. */
+    int rd_len;
+    int rd_num;
+    char *mm;           /* mmap address. */
+    size_t mm_len;
+    unsigned int next_avail_block;
+    int frame_len;
+};
+
+struct tpacket_info {
+    int fd;
+    struct tpacket_req3 req;
+    struct tp_ring rxring;
+    struct tp_ring txring;
+};
+
 struct netdev_rxq_linux {
     struct netdev_rxq up;
     bool is_tap;
@@ -110,6 +129,10 @@ struct netdev_linux {
 
     struct netdev_afxdp_tx_lock *tx_locks;  /* Array of locks for TX queues. */
 #endif
+
+    /* tpacket v3 information. */
+    struct tpacket_info **tps;
+    int n_tps;
 };
 
 static bool
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index f8e59bacfb13..edfc389ee6f2 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -36,9 +36,10 @@
 #include <linux/rtnetlink.h>
 #include <linux/sockios.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/socket.h>
 #include <sys/utsname.h>
-#include <netpacket/packet.h>
+//#include <netpacket/packet.h>
 #include <net/if.h>
 #include <net/if_arp.h>
 #include <net/route.h>
@@ -57,6 +58,7 @@
 #include "openvswitch/hmap.h"
 #include "netdev-afxdp.h"
 #include "netdev-provider.h"
+#include "netdev-tpacket.h"
 #include "netdev-vport.h"
 #include "netlink-notifier.h"
 #include "netlink-socket.h"
@@ -3315,6 +3317,26 @@ const struct netdev_class netdev_afxdp_class = {
     .rxq_recv = netdev_afxdp_rxq_recv,
 };
 #endif
+
+const struct netdev_class netdev_tpacket_class = {
+    NETDEV_LINUX_CLASS_COMMON,
+    .type = "tpacket",
+    .is_pmd = true,
+    .construct = netdev_linux_construct,
+    .destruct = netdev_linux_destruct,
+    .get_stats = netdev_linux_get_stats,
+    .get_features = netdev_linux_get_features,
+    .get_status = netdev_linux_get_status,
+    .set_config = netdev_tpacket_set_config,
+    .get_config = netdev_tpacket_get_config,
+    .reconfigure = netdev_tpacket_reconfigure,
+    .get_block_id = netdev_linux_get_block_id,
+    .get_numa_id = netdev_afxdp_get_numa_id,
+    .send = netdev_tpacket_batch_send,
+    .rxq_construct = netdev_linux_rxq_construct,
+    .rxq_destruct = netdev_linux_rxq_destruct,
+    .rxq_recv = netdev_tpacket_rxq_recv,
+};
 
 
 #define CODEL_N_QUEUES 0x0000
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index f109c4e66f0d..518d1dc6e02c 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -833,6 +833,7 @@ extern const struct netdev_class netdev_bsd_class;
 extern const struct netdev_class netdev_windows_class;
 #else
 extern const struct netdev_class netdev_linux_class;
+extern const struct netdev_class netdev_tpacket_class;
 #endif
 extern const struct netdev_class netdev_internal_class;
 extern const struct netdev_class netdev_tap_class;
diff --git a/lib/netdev-tpacket.c b/lib/netdev-tpacket.c
new file mode 100644
index 000000000000..798ce776838f
--- /dev/null
+++ b/lib/netdev-tpacket.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2019 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "netdev-linux-private.h"
+#include "netdev-linux.h"
+#include "netdev-tpacket.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "dp-packet.h"
+#include "dpif-netdev.h"
+#include "fatal-signal.h"
+#include "openvswitch/compiler.h"
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/list.h"
+#include "openvswitch/thread.h"
+#include "openvswitch/vlog.h"
+#include "packets.h"
+#include "socket-util.h"
+#include "util.h"
+
+COVERAGE_DEFINE(tpacket_rx_busy);
+COVERAGE_DEFINE(tpacket_tx_busy);
+
+VLOG_DEFINE_THIS_MODULE(netdev_tpacket);
+//static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+/* One block contains two frames. */
+#define TP_BLOCKSZ      4096
+#define TP_FRAMESZ      2048
+#define TP_NUM_DESCS    1024
+#define TP_BLOCKNR      1024
+#define TP_BLOCKNR_MASK (TP_BLOCKNR - 1)
+#define TP_FRAMENR      (TP_BLOCKNR * (TP_BLOCKSZ/TP_FRAMESZ))
+#define TP_FRAMENR_MASK (TP_FRAMENR -1)
+#define BATCH_SIZE      NETDEV_MAX_BURST
+
+#define barrier() __asm__ __volatile__("" : : : "memory")
+
+static struct tpacket_info *tpacket_configure(struct netdev_linux *dev);
+static int tpacket_configure_all(struct netdev_linux *dev);
+static void tpacket_destroy(struct tpacket_info *tp);
+static void tpacket_destroy_all(struct netdev_linux *dev);
+
+static void
+tpacket_fill_v3(struct tpacket_req3 *r)
+{
+    memset(r, 0, sizeof *r);
+
+    r->tp_block_size = TP_BLOCKSZ;  /* Minimal size of contiguous block. */
+    r->tp_frame_size = TP_FRAMESZ;  /* Size of frame. */
+    r->tp_block_nr = TP_BLOCKNR;    /* Number of blocks. */
+    r->tp_frame_nr = TP_FRAMENR;    /* Number of frames. */
+    r->tp_retire_blk_tov = 0;       /* Timeout in msecs. */
+    r->tp_sizeof_priv = 0;          /* Offset to private data area. */
+    r->tp_feature_req_word = 0;
+    //r->tp_feature_req_word = TP_FT_REQ_FILL_RXHASH;
+}
+
+int
+netdev_tpacket_set_config(struct netdev *netdev,
+                          const struct smap *args OVS_UNUSED,
+                          char **errp OVS_UNUSED)
+{
+    netdev_request_reconfigure(netdev);
+    return 0;
+}
+
+int
+netdev_tpacket_get_config(const struct netdev *netdev OVS_UNUSED,
+                          struct smap *args OVS_UNUSED)
+{
+    return 0;
+}
+
+static struct tpacket_info *
+tpacket_configure(struct netdev_linux *dev)
+{
+    struct tpacket_req3 req;
+    struct tpacket_info *tp;
+    struct sockaddr_ll ll;
+    int ver, fd, ifindex;
+    int error, i, noqdisc;
+
+    tp = xmalloc(sizeof *tp);
+    if (!tp) {
+        ovs_mutex_unlock(&dev->mutex);
+        return NULL;
+    }
+    memset(tp, 0, sizeof *tp);
+
+    tp->fd = fd = socket(PF_PACKET, SOCK_RAW, 0);
+    if (fd < 0) {
+        VLOG_ERR("tpacket: create PF_PACKET failed: %s", ovs_strerror(errno));
+        error = errno;
+        goto error;
+    }
+
+    ver = TPACKET_V3;
+    error = setsockopt(fd, SOL_PACKET, PACKET_VERSION, &ver, sizeof(ver));
+    if (error) {
+        VLOG_ERR("tpacket: set version failed: %s", ovs_strerror(errno));
+        goto error;
+    }
+
+    tpacket_fill_v3(&req);
+    error = setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof req);
+    if (error) {
+        VLOG_ERR("tpacket: set rx_ring failed: %s", ovs_strerror(errno));
+        goto error;
+    }
+    error = setsockopt(fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof req);
+    if (error) {
+        VLOG_ERR("tpacket: set tx_ring failed: %s", ovs_strerror(errno));
+        goto error;
+    }
+    tp->req = req;
+
+    /* Configure rx/tx ring. */
+    tp->rxring.mm_len = req.tp_block_size * req.tp_block_nr;
+    tp->rxring.mm = mmap(0, 2 * tp->rxring.mm_len, PROT_READ | PROT_WRITE,
+                         MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0);
+    if (!tp->rxring.mm) {
+        VLOG_ERR("tpacket: mmap rx_ring failed: %s", ovs_strerror(errno));
+        goto error;
+    }
+    tp->txring.mm_len = tp->rxring.mm_len;
+    tp->txring.mm = tp->rxring.mm + tp->rxring.mm_len;
+
+    tp->rxring.rd_num = tp->txring.rd_num = req.tp_block_nr;
+    tp->rxring.rd_len = tp->txring.rd_len =
+                        req.tp_block_nr * sizeof *tp->rxring.rd;
+
+    tp->rxring.rd = xmalloc(tp->rxring.rd_len);
+    if (!tp->rxring.rd) {
+        return NULL;
+    }
+    memset(tp->rxring.rd, 0, tp->rxring.rd_len);
+
+    tp->txring.rd = xmalloc(tp->txring.rd_len);
+    if (!tp->txring.rd) {
+        return NULL;
+    }
+    memset(tp->txring.rd, 0, tp->txring.rd_len);
+
+    for (i = 0; i < tp->rxring.rd_num; i++) {
+        tp->rxring.rd[i].iov_base = tp->rxring.mm + (i * req.tp_block_size);
+        tp->rxring.rd[i].iov_len = req.tp_block_size;
+    }
+    for (i = 0; i < tp->txring.rd_num; i++) {
+        tp->txring.rd[i].iov_base = tp->txring.mm + (i * req.tp_block_size);
+        tp->txring.rd[i].iov_len = req.tp_block_size;
+    }
+
+    noqdisc = 1;
+    setsockopt(fd, SOL_PACKET, PACKET_QDISC_BYPASS,
+               &noqdisc, sizeof(noqdisc));
+
+    ifindex = linux_get_ifindex(netdev_get_name(&dev->up));
+
+    ll.sll_family = PF_PACKET;
+    ll.sll_protocol = htons(ETH_P_ALL);
+    ll.sll_ifindex = ifindex;
+    ll.sll_hatype = 0;
+    ll.sll_pkttype = 0;
+    ll.sll_halen = 0;
+
+    error = bind(fd, (struct sockaddr *)&ll, sizeof ll);
+    if (error) {
+        VLOG_ERR("tpacket: bind failed: %s", ovs_strerror(errno));
+        goto error_unmap;
+    }
+
+    return tp;
+
+error_unmap:
+    munmap(tp->rxring.mm, tp->rxring.mm_len * 2);
+error:
+    if (tp) {
+        free(tp);
+    }
+    if (fd >= 0) {
+        close(fd);
+    }
+
+    return NULL;
+}
+
+static int
+tpacket_configure_all(struct netdev_linux *dev)
+{
+    int n_tps, i;
+
+    n_tps = dev->n_tps;
+    dev->tps = calloc(n_tps, sizeof(struct tpacket_info *));
+
+    for (i = 0; i < n_tps; i++) {
+        VLOG_INFO("tpacket: configure %dth queue.", i);
+        dev->tps[i] = tpacket_configure(dev);
+        if (!dev->tps[i]) {
+            VLOG_ERR("tpacket: configure %dth queue failed.", i);
+            goto error;
+        }
+    }
+    return 0;
+
+error:
+    tpacket_destroy_all(dev);
+    return EINVAL;
+}
+
+static void
+tpacket_destroy(struct tpacket_info *tp)
+{
+    if (!tp) {
+        return;
+    }
+    munmap(tp->rxring.mm, tp->rxring.mm_len * 2);  /* Both rx and tx. */
+    close(tp->fd);
+    free(tp->rxring.rd);
+    free(tp->txring.rd);
+    free(tp);
+}
+
+static void
+tpacket_destroy_all(struct netdev_linux *dev)
+{
+    int i;
+
+    if (!dev->tps) {
+        return;
+    }
+    for (i = 0; i < dev->n_tps; i++) {
+        tpacket_destroy(dev->tps[i]);
+    }
+}
+
+int
+netdev_tpacket_reconfigure(struct netdev *netdev)
+{
+    struct netdev_linux *dev = netdev_linux_cast(netdev);
+    int err = 0;
+
+    ovs_mutex_lock(&dev->mutex);
+
+    netdev->n_rxq = 1;
+    dev->n_tps = netdev->n_rxq;
+    tpacket_destroy_all(dev);
+
+    err = tpacket_configure_all(dev);
+    if (err) {
+        VLOG_ERR("%s: tpacket reconfiguration failed.",
+                 netdev_get_name(netdev));
+    }
+    netdev_change_seq_changed(netdev);
+    ovs_mutex_unlock(&dev->mutex);
+
+    return err;
+}
+
+static inline uint32_t
+get_block_status(struct tpacket_block_desc *desc)
+{
+    barrier();
+    return desc->hdr.bh1.block_status;
+}
+
+static inline void
+set_block_status(struct tpacket_block_desc *desc, uint32_t status)
+{
+    desc->hdr.bh1.block_status = status;
+    barrier();
+}
+
+static inline uint32_t
+get_num_pkts(struct tpacket_block_desc *desc)
+{
+    return desc->hdr.bh1.num_pkts;
+}
+
+static inline uint32_t
+first_pkt_ofs(struct tpacket_block_desc *desc)
+{
+    return desc->hdr.bh1.offset_to_first_pkt;
+}
+
+static uint64_t block_seq_num = 0;
+static void OVS_UNUSED
+check_seq_num(struct tpacket_block_desc *desc)
+{
+    uint64_t seq = desc->hdr.bh1.seq_num;
+
+    if (block_seq_num + 1 != seq) {
+        VLOG_ERR("seq no %"PRIu64" + 1 != %"PRIu64,
+                 block_seq_num, seq);
+    } else {
+        block_seq_num = seq;
+    }
+}
+int
+netdev_tpacket_rxq_recv(struct netdev_rxq *rxq_,
+                        struct dp_packet_batch *batch,
+                        int *qfill)
+{
+    struct netdev_rxq_linux *rx = netdev_rxq_linux_cast(rxq_);
+    struct netdev *netdev = rx->up.netdev;
+    struct netdev_linux *dev = netdev_linux_cast(netdev);
+    int qid = rxq_->queue_id;
+    struct tpacket_block_desc *desc;
+    struct tpacket_info *tp;
+    struct tp_ring *rxring;
+    unsigned int block_num, n_pkts = 0;
+
+    tp = dev->tps[qid];
+    if (!tp) {
+        return EAGAIN;
+    }
+    rx->fd = tp->fd;
+    rxring = &tp->rxring;
+    block_num = rxring->next_avail_block;
+    dp_packet_batch_init(batch);
+
+    while (n_pkts < BATCH_SIZE) {
+        struct tpacket3_hdr *tphdr;
+        struct dp_packet *packet;
+        uint32_t num_pkts;
+        char *data;
+        int i;
+
+        block_num = block_num & TP_BLOCKNR_MASK;
+        desc = (struct tpacket_block_desc *)rxring->rd[block_num].iov_base;
+        while ((get_block_status(desc) & TP_STATUS_USER) == 0) {
+            if (batch->count == 0) {
+#if 0
+                struct pollfd pfd;
+                memset(&pfd, 0, sizeof pfd);
+                pfd.fd = tp->fd;
+                pfd.events = POLLIN | POLLERR;
+                pfd.events = 0;
+                poll(&pfd, 1, 1);
+#endif
+                COVERAGE_INC(tpacket_rx_busy);
+                return EAGAIN;
+            } else {
+                goto out;
+            }
+        }
+
+        check_seq_num(desc);
+        num_pkts = get_num_pkts(desc);
+        tphdr = (struct tpacket3_hdr *)
+                ((char *)desc + first_pkt_ofs(desc));
+
+        /* A block might have multiple frames(packets). */
+        for (i = 0; i < num_pkts; i++) {
+            data = (char *)tphdr + tphdr->tp_mac;
+            packet = dp_packet_clone_data_with_headroom(data,
+                                                        tphdr->tp_snaplen,
+                                                        DP_NETDEV_HEADROOM);
+            dp_packet_set_size(packet, tphdr->tp_snaplen);
+            dp_packet_set_rss_hash(packet, tphdr->hv1.tp_rxhash);
+            dp_packet_batch_add(batch, packet);
+
+            tphdr = (struct tpacket3_hdr *)((char *)tphdr +
+                                            tphdr->tp_next_offset);
+            barrier();
+            n_pkts++;
+        }
+
+        block_num++;
+        rxring->next_avail_block++;
+        set_block_status(desc, TP_STATUS_KERNEL);
+    }
+
+out:
+    if (qfill) {
+        *qfill = 0;
+    }
+
+    return 0;
+}
+
+static inline struct tpacket3_hdr *
+get_next_tx_frame(struct tp_ring *txring, int n)
+{
+    char *start = txring->rd[0].iov_base;
+
+    return (struct tpacket3_hdr *)(start + (n * TP_FRAMESZ));
+}
+
+int
+netdev_tpacket_batch_send(struct netdev *netdev, int qid,
+                          struct dp_packet_batch *batch,
+                          bool concurrent_txq OVS_UNUSED)
+{
+    struct netdev_linux *dev = netdev_linux_cast(netdev);
+    struct dp_packet *packet;
+    struct tpacket_info *tp;
+    struct tp_ring *txring;
+    unsigned int frame_num;
+    int error = 0;
+    int retries = 3;
+
+    tp = dev->tps[qid];
+    if (!tp) {
+        error = EAGAIN;
+        goto out;
+    }
+    txring = &tp->txring;
+    frame_num = txring->next_avail_block;
+
+    DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+        struct tpacket3_hdr *tphdr;
+        int size;
+
+        frame_num = frame_num & TP_FRAMENR_MASK;
+        tphdr = get_next_tx_frame(txring, frame_num);
+#if 0
+        if (!(tphdr->tp_status & TP_STATUS_AVAILABLE)) {
+            COVERAGE_INC(tpacket_tx_busy);
+        }
+#endif
+        if (tphdr->tp_status &
+           (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING)) {
+            barrier();
+            COVERAGE_INC(tpacket_tx_busy);
+            error = EAGAIN;
+            goto out;
+        }
+
+        size = dp_packet_size(packet);
+        tphdr->tp_snaplen = size;
+        tphdr->tp_len = size;
+        tphdr->tp_next_offset = 0;
+
+        memcpy((char *)tphdr + TPACKET3_HDRLEN - sizeof(struct sockaddr_ll),
+               dp_packet_data(packet), size);
+
+        frame_num++;
+        txring->next_avail_block++;
+        barrier();
+        tphdr->tp_status = TP_STATUS_SEND_REQUEST;
+    }
+
+kick_retry:
+    error = sendto(tp->fd, NULL, 0, MSG_DONTWAIT, NULL, 0);
+    if (error < 0) {
+        if (retries-- && errno == EAGAIN)  {
+            COVERAGE_INC(tpacket_tx_busy);
+            goto kick_retry;
+        } else {
+            goto out;
+        }
+    }
+
+    return 0;
+
+out:
+    dp_packet_delete_batch(batch, true);
+    return error;
+}
diff --git a/lib/netdev-tpacket.h b/lib/netdev-tpacket.h
new file mode 100644
index 000000000000..2a80f962e0b7
--- /dev/null
+++ b/lib/netdev-tpacket.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, 2019 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETDEV_TPACKET_H
+#define NETDEV_TPACKET_H 1
+
+#include <stdint.h>
+#include <stdbool.h>
+
+struct dp_packet;
+struct dp_packet_batch;
+struct netdev;
+struct netdev_custom_stats;
+struct netdev_rxq;
+struct netdev_stats;
+struct smap;
+
+int netdev_tpacket_rxq_recv(struct netdev_rxq *rxq_,
+                            struct dp_packet_batch *batch,
+                            int *qfill);
+int netdev_tpacket_batch_send(struct netdev *netdev_, int qid,
+                            struct dp_packet_batch *batch,
+                            bool concurrent_txq);
+int netdev_tpacket_set_config(struct netdev *netdev, const struct smap *args,
+                              char **errp);
+int netdev_tpacket_get_config(const struct netdev *netdev, struct smap *args);
+int netdev_tpacket_get_custom_stats(const struct netdev *netdev,
+                                    struct netdev_custom_stats *custom_stats);
+int netdev_tpacket_reconfigure(struct netdev *netdev);
+#endif /* netdev-tpacket.h */
diff --git a/lib/netdev.c b/lib/netdev.c
index 405c98c687fa..3710834521d5 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -145,6 +145,7 @@ netdev_initialize(void)
 
 #ifdef __linux__
         netdev_register_provider(&netdev_linux_class);
+        netdev_register_provider(&netdev_tpacket_class);
         netdev_register_provider(&netdev_internal_class);
         netdev_register_provider(&netdev_tap_class);
         netdev_vport_tunnel_register();
-- 
2.7.4



More information about the dev mailing list