new file mode 100644
@@ -0,0 +1,455 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2022 Intel Corporation
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+
+#include <rte_common.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memcpy.h>
+#include <rte_eal.h>
+#include <rte_launch.h>
+#include <rte_atomic.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+#include <rte_lcore.h>
+#include <rte_per_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_interrupts.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_string_fns.h>
+#include <rte_graph_worker.h>
+
+#include "node.h"
+
+#define MAX_PKT_BURST 32
+#define MEMPOOL_CACHE_SIZE 256
+#define RTE_TEST_RX_DESC_DEFAULT 1024
+#define RTE_TEST_TX_DESC_DEFAULT 1024
+
+/* ethernet addresses of ports */
+struct rte_ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS];
+
+static struct rte_eth_conf default_port_conf = {
+ .rxmode = {
+ .mq_mode = RTE_ETH_MQ_RX_RSS,
+ .split_hdr_size = 0,
+ },
+ .rx_adv_conf = {
+ .rss_conf = {
+ .rss_key = NULL,
+ .rss_hf = RTE_ETH_RSS_IP,
+ },
+ },
+ .txmode = {
+ .mq_mode = RTE_ETH_MQ_TX_NONE,
+ },
+};
+
+static struct rte_mempool *l2fwd_pktmbuf_pool;
+
+static volatile bool force_quit;
+static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT;
+static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT;
+static uint16_t nb_rxq = 1;
+
+/* Lcore conf */
+struct lcore_conf {
+ int enable;
+ uint16_t port_id;
+
+ struct rte_graph *graph;
+ char name[RTE_GRAPH_NAMESIZE];
+ rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+static struct lcore_conf l2fwd_lcore_conf[RTE_MAX_LCORE];
+
+#define L2FWD_GRAPH_NAME_PREFIX "l2fwd_graph_"
+
+static int
+l2fwd_graph_loop(void *conf)
+{
+ struct lcore_conf *lconf;
+ struct rte_graph *graph;
+ uint32_t lcore_id;
+
+ RTE_SET_USED(conf);
+
+ lcore_id = rte_lcore_id();
+ lconf = &l2fwd_lcore_conf[lcore_id];
+ graph = lconf->graph;
+
+ if (!graph) {
+ RTE_LOG(INFO, L2FWD_GRAPH, "Lcore %u has nothing to do\n",
+ lcore_id);
+ return 0;
+ }
+
+ RTE_LOG(INFO, L2FWD_GRAPH,
+ "Entering graph loop on lcore %u, graph %s\n",
+ lcore_id, graph->name);
+
+ while (likely(!force_quit))
+ rte_graph_walk(graph);
+
+ RTE_LOG(INFO, L2FWD_GRAPH,
+ "Leaving graph loop on lcore %u, graph %s\n",
+ lcore_id, graph->name);
+
+ return 0;
+}
+
+static void
+print_stats(void)
+{
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ struct rte_graph_cluster_stats_param s_param;
+ struct rte_graph_cluster_stats *stats;
+ const char *pattern = L2FWD_GRAPH_NAME_PREFIX "*";
+
+ /* Prepare stats object */
+ memset(&s_param, 0, sizeof(s_param));
+ s_param.f = stdout;
+ s_param.socket_id = SOCKET_ID_ANY;
+ s_param.graph_patterns = &pattern;
+ s_param.nb_graph_patterns = 1;
+
+ stats = rte_graph_cluster_stats_create(&s_param);
+ if (stats == NULL)
+ rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
+
+ while (!force_quit) {
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+ rte_graph_cluster_stats_get(stats, 0);
+ rte_delay_ms(1E3);
+ }
+
+ rte_graph_cluster_stats_destroy(stats);
+}
+
+static void
+signal_handler(int signum)
+{
+ if (signum == SIGINT || signum == SIGTERM) {
+ printf("\n\nSignal %d received, preparing to exit...\n",
+ signum);
+ force_quit = true;
+ }
+}
+
+enum {
+ /* Long options mapped to a short option */
+
+ /* First long only option value must be >= 256, so that we won't
+ * conflict with short options
+ */
+ CMD_LINE_OPT_MIN_NUM = 256,
+ CMD_LINE_OPT_RXQ_NUM,
+};
+
+int
+main(int argc, char **argv)
+{
+ static const char *default_patterns[] = {
+ "l2fwd_pkt_rx-0-0",
+ "l2fwd_pkt_fwd",
+ "l2fwd_pkt_tx",
+ "l2fwd_pkt_drop"
+ };
+ struct rte_graph_param graph_conf;
+ union l2fwd_node_ctx node_ctx;
+ struct lcore_conf *lconf;
+ uint16_t nb_ports_available = 0;
+ uint16_t nb_ports;
+ uint16_t portid;
+ uint16_t fwd_portid = 0;
+ unsigned int nb_lcores = 0;
+ unsigned int lcore_id;
+ unsigned int nb_mbufs;
+ rte_graph_t main_graph_id = RTE_GRAPH_ID_INVALID;
+ rte_graph_t graph_id;
+ uint16_t n;
+ int ret;
+
+ /* init EAL */
+ ret = rte_eal_init(argc, argv);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
+ argc -= ret;
+ argv += ret;
+
+ force_quit = false;
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+
+ nb_ports = rte_eth_dev_count_avail();
+ if (nb_ports == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ nb_lcores = 2;
+ nb_mbufs = RTE_MAX(nb_ports * ((nb_rxd * nb_rxq) + nb_txd + MAX_PKT_BURST +
+ nb_lcores * MEMPOOL_CACHE_SIZE), 8192U);
+
+ /* create the mbuf pool */
+ l2fwd_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", nb_mbufs,
+ MEMPOOL_CACHE_SIZE, 0,
+ RTE_MBUF_DEFAULT_BUF_SIZE,
+ rte_socket_id());
+ if (l2fwd_pktmbuf_pool == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n");
+
+ /* Initialise each port */
+ RTE_ETH_FOREACH_DEV(portid) {
+ struct rte_eth_conf local_port_conf = default_port_conf;
+ struct rte_eth_dev_info dev_info;
+ struct rte_eth_rxconf rxq_conf;
+ struct rte_eth_txconf txq_conf;
+
+ nb_ports_available++;
+
+ /* init port */
+ printf("Initializing port %u... ", portid);
+ fflush(stdout);
+
+ ret = rte_eth_dev_info_get(portid, &dev_info);
+ if (ret != 0)
+ rte_exit(EXIT_FAILURE,
+ "Error during getting device (port %u) info: %s\n",
+ portid, strerror(-ret));
+
+ if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)
+ local_port_conf.txmode.offloads |=
+ RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
+
+ local_port_conf.rx_adv_conf.rss_conf.rss_hf &=
+ dev_info.flow_type_rss_offloads;
+ if (local_port_conf.rx_adv_conf.rss_conf.rss_hf !=
+ default_port_conf.rx_adv_conf.rss_conf.rss_hf) {
+ printf("Port %u modified RSS hash function based on "
+ "hardware support,"
+ "requested:%#" PRIx64 " configured:%#" PRIx64
+ "\n",
+ portid, default_port_conf.rx_adv_conf.rss_conf.rss_hf,
+ local_port_conf.rx_adv_conf.rss_conf.rss_hf);
+ }
+
+ ret = rte_eth_dev_configure(portid, nb_rxq, 1, &local_port_conf);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n",
+ ret, portid);
+
+ ret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE,
+ "Cannot adjust number of descriptors: err=%d, port=%u\n",
+ ret, portid);
+
+ ret = rte_eth_macaddr_get(portid,
+ &l2fwd_ports_eth_addr[portid]);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE,
+ "Cannot get MAC address: err=%d, port=%u\n",
+ ret, portid);
+
+ /* init RX queues */
+ fflush(stdout);
+ for (n = 0; n < nb_rxq; n++) {
+ rxq_conf = dev_info.default_rxconf;
+ rxq_conf.offloads = local_port_conf.rxmode.offloads;
+ ret = rte_eth_rx_queue_setup(portid, n, nb_rxd,
+ rte_eth_dev_socket_id(portid),
+ &rxq_conf,
+ l2fwd_pktmbuf_pool);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE,
+ "rte_eth_rx_queue_setup:err=%d, port=%u, queue=%u\n",
+ ret, portid, n);
+ }
+
+ /* init one TX queue on each port */
+ fflush(stdout);
+ txq_conf = dev_info.default_txconf;
+ txq_conf.offloads = local_port_conf.txmode.offloads;
+ ret = rte_eth_tx_queue_setup(portid, 0, nb_txd,
+ rte_eth_dev_socket_id(portid),
+ &txq_conf);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d, port=%u\n",
+ ret, portid);
+
+ /* Start device */
+ ret = rte_eth_dev_start(portid);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err=%d, port=%u\n",
+ ret, portid);
+
+ ret = rte_eth_promiscuous_enable(portid);
+ if (ret < 0)
+ printf("Failed to set port %u promiscuous mode!\n", portid);
+
+ printf("done:\n");
+
+ printf("Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n",
+ portid,
+ l2fwd_ports_eth_addr[portid].addr_bytes[0],
+ l2fwd_ports_eth_addr[portid].addr_bytes[1],
+ l2fwd_ports_eth_addr[portid].addr_bytes[2],
+ l2fwd_ports_eth_addr[portid].addr_bytes[3],
+ l2fwd_ports_eth_addr[portid].addr_bytes[4],
+ l2fwd_ports_eth_addr[portid].addr_bytes[5]);
+
+ fwd_portid = portid;
+ }
+
+ if (!nb_ports_available) {
+ rte_exit(EXIT_FAILURE,
+ "All available ports are disabled. Please set portmask.\n");
+ }
+
+ l2fwd_rx_node_init(0, nb_rxq);
+
+ memset(&node_ctx, 0, sizeof(node_ctx));
+ node_ctx.fwd_ctx.dest_port = fwd_portid;
+ l2fwd_node_ctx_add(l2fwd_get_pkt_fwd_node_id(), &node_ctx);
+
+ memset(&node_ctx, 0, sizeof(node_ctx));
+ node_ctx.rxtx_ctx.port = fwd_portid;
+ node_ctx.rxtx_ctx.queue = 0;
+ l2fwd_node_ctx_add(l2fwd_get_pkt_tx_node_id(), &node_ctx);
+
+ /* Need to set the node Lcore affinity before creating graph */
+ for (n = 0, lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+ if (rte_lcore_is_enabled(lcore_id) == 0 ||
+ rte_get_main_lcore() == lcore_id)
+ continue;
+
+ if (n < RTE_DIM(default_patterns)) {
+ ret = rte_node_set_lcore_affinity(default_patterns[n], lcore_id);
+ if (ret == 0)
+ printf("Set node %s affinity to Lcore %u\n",
+ default_patterns[n], lcore_id);
+ }
+
+ n++;
+ }
+
+ for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+ lconf = &l2fwd_lcore_conf[lcore_id];
+
+ if (rte_lcore_is_enabled(lcore_id) == 0 ||
+ rte_get_main_lcore() == lcore_id) {
+ lconf->graph_id = RTE_GRAPH_ID_INVALID;
+ continue;
+ }
+
+ if (main_graph_id != RTE_GRAPH_ID_INVALID) {
+ struct rte_graph_clone_param clone_prm;
+
+ snprintf(lconf->name, sizeof(lconf->name), "cloned-%u", lcore_id);
+ memset(&clone_prm, 0, sizeof(clone_prm));
+ clone_prm.lcore_id = lcore_id;
+ graph_id = rte_graph_clone(main_graph_id, lconf->name, &clone_prm);
+ if (graph_id == RTE_GRAPH_ID_INVALID)
+ rte_exit(EXIT_FAILURE,
+ "Failed to clone graph for lcore %u\n",
+ lcore_id);
+
+ /* full cloned graph name */
+ snprintf(lconf->name, sizeof(lconf->name), "%s",
+ rte_graph_id_to_name(graph_id));
+ lconf->graph_id = graph_id;
+ lconf->graph = rte_graph_lookup(lconf->name);
+ if (!lconf->graph)
+ rte_exit(EXIT_FAILURE,
+ "Failed to lookup graph %s\n",
+ lconf->name);
+ //rte_graph_obj_dump(stdout, lconf->graph, true);
+ continue;
+ }
+
+ memset(&graph_conf, 0, sizeof(graph_conf));
+ graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+ graph_conf.nb_node_patterns = RTE_DIM(default_patterns);
+ graph_conf.node_patterns = default_patterns;
+
+ snprintf(lconf->name, sizeof(lconf->name), L2FWD_GRAPH_NAME_PREFIX "%u",
+ lcore_id);
+
+ graph_id = rte_graph_create(lconf->name, &graph_conf);
+ if (graph_id == RTE_GRAPH_ID_INVALID)
+ rte_exit(EXIT_FAILURE,
+ "Failed to create graph for lcore %u\n",
+ lcore_id);
+
+ lconf->graph_id = graph_id;
+ lconf->graph = rte_graph_lookup(lconf->name);
+ if (!lconf->graph)
+ rte_exit(EXIT_FAILURE,
+ "Failed to lookup graph %s\n",
+ lconf->name);
+ //rte_graph_obj_dump(stdout, lconf->graph, true);
+ main_graph_id = graph_id;
+ }
+
+ rte_eal_mp_remote_launch(l2fwd_graph_loop, NULL, SKIP_MAIN);
+
+ if (rte_graph_has_stats_feature())
+ print_stats();
+
+ ret = 0;
+
+ printf("Waiting all graphs to quit ...\n");
+ RTE_LCORE_FOREACH_WORKER(lcore_id) {
+ if (rte_eal_wait_lcore(lcore_id) < 0) {
+ ret = -1;
+ break;
+ }
+
+ lconf = &l2fwd_lcore_conf[lcore_id];
+ printf("Lcore %u with graph %s quit!\n", lcore_id,
+ lconf->graph != NULL ? lconf->name : "Empty");
+ }
+
+ RTE_LCORE_FOREACH_WORKER(lcore_id) {
+ graph_id = l2fwd_lcore_conf[lcore_id].graph_id;
+ if (graph_id != RTE_GRAPH_ID_INVALID)
+ rte_graph_destroy(graph_id);
+ }
+
+ RTE_ETH_FOREACH_DEV(portid) {
+ printf("Closing port %d...", portid);
+ ret = rte_eth_dev_stop(portid);
+ if (ret != 0)
+ printf("rte_eth_dev_stop: err=%d, port=%d\n",
+ ret, portid);
+ rte_eth_dev_close(portid);
+ printf(" Done\n");
+ }
+ printf("Bye...\n");
+
+ return ret;
+}
new file mode 100644
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(C) 2020 Marvell International Ltd.
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+allow_experimental_apis = true
+
+deps += ['graph', 'eal', 'lpm', 'ethdev']
+
+if dpdk_conf.has('RTE_MEMPOOL_RING')
+ deps += 'mempool_ring'
+endif
+
+if dpdk_conf.has('RTE_NET_ICE')
+ deps += 'net_ice'
+endif
+
+sources = files(
+ 'node.c',
+ 'main.c'
+)
+
new file mode 100644
@@ -0,0 +1,263 @@
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_mbuf.h>
+
+#include "node.h"
+
+static struct l2fwd_node_ctx_head l2fwd_node_ctx_list =
+ STAILQ_HEAD_INITIALIZER(l2fwd_node_ctx_list);
+static rte_spinlock_t l2fwd_node_ctx_lock = RTE_SPINLOCK_INITIALIZER;
+
+int
+l2fwd_node_ctx_add(rte_node_t node_id, union l2fwd_node_ctx *ctx)
+{
+ struct l2fwd_node_ctx_elem *ctx_elem, *temp;
+ int ret;
+
+ ctx_elem = calloc(1, sizeof(*temp));
+ if (ctx_elem == NULL) {
+ RTE_LOG(ERR, L2FWD_GRAPH,
+ "Failed to calloc node %u context object\n", node_id);
+ return -ENOMEM;
+ }
+
+ ctx_elem->id = node_id;
+ rte_memcpy(&ctx_elem->ctx, ctx, sizeof(*ctx));
+
+ rte_spinlock_lock(&l2fwd_node_ctx_lock);
+
+ STAILQ_FOREACH(temp, &l2fwd_node_ctx_list, next) {
+ if (temp->id == node_id) {
+ ret = -EEXIST;
+ RTE_LOG(ERR, L2FWD_GRAPH,
+ "The node %u context exists\n", node_id);
+ rte_spinlock_unlock(&l2fwd_node_ctx_lock);
+ goto fail;
+ }
+ }
+
+ STAILQ_INSERT_TAIL(&l2fwd_node_ctx_list, ctx_elem, next);
+
+ rte_spinlock_unlock(&l2fwd_node_ctx_lock);
+
+ return 0;
+
+fail:
+ free(temp);
+ return ret;
+}
+
+int
+l2fwd_node_ctx_del(rte_node_t node_id)
+{
+ struct l2fwd_node_ctx_elem *ctx_elem, *found = NULL;
+
+ rte_spinlock_lock(&l2fwd_node_ctx_lock);
+
+ STAILQ_FOREACH(ctx_elem, &l2fwd_node_ctx_list, next) {
+ if (ctx_elem->id == node_id) {
+ STAILQ_REMOVE(&l2fwd_node_ctx_list, ctx_elem, l2fwd_node_ctx_elem, next);
+ found = ctx_elem;
+ break;
+ }
+ }
+
+ rte_spinlock_unlock(&l2fwd_node_ctx_lock);
+
+ if (found) {
+ free(found);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+static int
+l2fwd_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+ struct l2fwd_node_ctx_elem *ctx_elem;
+ int ret = -1;
+
+ RTE_SET_USED(graph);
+
+ rte_spinlock_lock(&l2fwd_node_ctx_lock);
+
+ STAILQ_FOREACH(ctx_elem, &l2fwd_node_ctx_list, next) {
+ if (ctx_elem->id == node->id) {
+ rte_memcpy(node->ctx, ctx_elem->ctx.ctx, RTE_NODE_CTX_SZ);
+ ret = 0;
+ break;
+ }
+ }
+
+ rte_spinlock_unlock(&l2fwd_node_ctx_lock);
+
+ return ret;
+}
+
+static uint16_t
+l2fwd_pkt_rx_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t cnt)
+{
+ struct l2fwd_node_rxtx_ctx *ctx = (struct l2fwd_node_rxtx_ctx *)node->ctx;
+ uint16_t n_pkts;
+
+ RTE_SET_USED(objs);
+ RTE_SET_USED(cnt);
+
+ n_pkts = rte_eth_rx_burst(ctx->port, ctx->queue, (struct rte_mbuf **)node->objs,
+ RTE_GRAPH_BURST_SIZE);
+ if (!n_pkts)
+ return 0;
+
+ node->idx = n_pkts;
+
+ rte_node_next_stream_move(graph, node, L2FWD_RX_NEXT_PKT_FWD);
+
+ return n_pkts;
+}
+
+static struct rte_node_register l2fwd_pkt_rx_node_base = {
+ .name = "l2fwd_pkt_rx",
+ .flags = RTE_NODE_SOURCE_F,
+ .init = l2fwd_node_init,
+ .process = l2fwd_pkt_rx_node_process,
+
+ .nb_edges = L2FWD_RX_NEXT_MAX,
+ .next_nodes = {
+ [L2FWD_RX_NEXT_PKT_FWD] = "l2fwd_pkt_fwd",
+ },
+};
+
+int l2fwd_rx_node_init(uint16_t portid, uint16_t nb_rxq)
+{
+ char name[RTE_NODE_NAMESIZE];
+ union l2fwd_node_ctx ctx;
+ uint32_t id;
+ uint16_t n;
+
+ for (n = 0; n < nb_rxq; n++) {
+ snprintf(name, sizeof(name), "%u-%u", portid, n);
+
+ /* Clone a new rx node with same edges as parent */
+ id = rte_node_clone(l2fwd_pkt_rx_node_base.id, name);
+ if (id == RTE_NODE_ID_INVALID)
+ return -EIO;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.rxtx_ctx.port = portid;
+ ctx.rxtx_ctx.queue = n;
+ l2fwd_node_ctx_add(id, &ctx);
+ }
+
+ return 0;
+}
+
+static uint16_t
+l2fwd_pkt_fwd_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t cnt)
+{
+ struct l2fwd_node_fwd_ctx *ctx = (struct l2fwd_node_fwd_ctx *)node->ctx;
+ struct rte_mbuf *mbuf, **pkts;
+ struct rte_ether_addr *dest;
+ struct rte_ether_hdr *eth;
+ uint16_t dest_port;
+ uint16_t i;
+ void *tmp;
+
+ dest_port = ctx->dest_port;
+ dest = &l2fwd_ports_eth_addr[dest_port];
+ pkts = (struct rte_mbuf **)objs;
+
+ for (i = 0; i < cnt; i++) {
+ mbuf = pkts[i];
+
+ eth = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+
+ /* 02:00:00:00:00:xx */
+ tmp = ð->dst_addr.addr_bytes[0];
+ *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dest_port << 40);
+
+ /* src addr */
+ rte_ether_addr_copy(dest, ð->src_addr);
+ }
+
+ rte_node_enqueue(graph, node, L2FWD_FWD_NEXT_PKT_TX, objs, cnt);
+
+ return cnt;
+}
+
+static struct rte_node_register l2fwd_pkt_fwd_node = {
+ .name = "l2fwd_pkt_fwd",
+ .init = l2fwd_node_init,
+ .process = l2fwd_pkt_fwd_node_process,
+
+ .nb_edges = L2FWD_FWD_NEXT_MAX,
+ .next_nodes = {
+ [L2FWD_FWD_NEXT_PKT_TX] = "l2fwd_pkt_tx",
+ },
+};
+
+rte_node_t
+l2fwd_get_pkt_fwd_node_id(void)
+{
+ return l2fwd_pkt_fwd_node.id;
+}
+
+static uint16_t
+l2fwd_pkt_tx_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ struct l2fwd_node_rxtx_ctx *ctx = (struct l2fwd_node_rxtx_ctx *)node->ctx;
+ uint16_t count;
+
+ count = rte_eth_tx_burst(ctx->port, ctx->queue, (struct rte_mbuf **)objs,
+ nb_objs);
+ if (count != nb_objs)
+ rte_node_enqueue(graph, node, L2FWD_TX_NEXT_PKT_DROP,
+ &objs[count], nb_objs - count);
+
+ return count;
+}
+
+static struct rte_node_register l2fwd_pkt_tx_node = {
+ .name = "l2fwd_pkt_tx",
+ .init = l2fwd_node_init,
+ .process = l2fwd_pkt_tx_node_process,
+
+ .nb_edges = L2FWD_TX_NEXT_MAX,
+ .next_nodes = {
+ [L2FWD_TX_NEXT_PKT_DROP] = "l2fwd_pkt_drop",
+ },
+};
+
+rte_node_t
+l2fwd_get_pkt_tx_node_id(void)
+{
+ return l2fwd_pkt_tx_node.id;
+}
+
+static uint16_t
+l2fwd_pkt_drop_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(node);
+ RTE_SET_USED(graph);
+
+ rte_pktmbuf_free_bulk((struct rte_mbuf **)objs, nb_objs);
+
+ return nb_objs;
+}
+
+static struct rte_node_register l2fwd_pkt_drop_node = {
+ .name = "l2fwd_pkt_drop",
+ .process = l2fwd_pkt_drop_node_process,
+};
+
+RTE_NODE_REGISTER(l2fwd_pkt_rx_node_base);
+RTE_NODE_REGISTER(l2fwd_pkt_fwd_node);
+RTE_NODE_REGISTER(l2fwd_pkt_tx_node);
+RTE_NODE_REGISTER(l2fwd_pkt_drop_node);
new file mode 100644
@@ -0,0 +1,64 @@
+#ifndef __NODE_H__
+#define __NODE_H__
+
+#include <inttypes.h>
+#include <sys/queue.h>
+
+#include <rte_common.h>
+#include <rte_eal.h>
+
+#include "rte_graph.h"
+#include "rte_graph_worker.h"
+
+#define RTE_LOGTYPE_L2FWD_GRAPH RTE_LOGTYPE_USER1
+
+enum l2fwd_rx_next_nodes {
+ L2FWD_RX_NEXT_PKT_FWD,
+ L2FWD_RX_NEXT_MAX,
+};
+
+enum l2fwd_fwd_next_nodes {
+ L2FWD_FWD_NEXT_PKT_TX,
+ L2FWD_FWD_NEXT_MAX,
+};
+
+enum l2fwd_tx_next_nodes {
+ L2FWD_TX_NEXT_PKT_DROP,
+ L2FWD_TX_NEXT_MAX,
+};
+
+struct l2fwd_node_rxtx_ctx {
+ uint16_t port;
+ uint16_t queue;
+};
+
+struct l2fwd_node_fwd_ctx {
+ uint16_t dest_port;
+};
+
+union l2fwd_node_ctx {
+ struct l2fwd_node_rxtx_ctx rxtx_ctx;
+ struct l2fwd_node_fwd_ctx fwd_ctx;
+ uint8_t ctx[RTE_NODE_CTX_SZ];
+};
+
+struct l2fwd_node_ctx_elem {
+ STAILQ_ENTRY(l2fwd_node_ctx_elem) next;
+
+ rte_node_t id;
+
+ union l2fwd_node_ctx ctx;
+};
+
+STAILQ_HEAD(l2fwd_node_ctx_head, l2fwd_node_ctx_elem);
+
+extern struct rte_ether_addr l2fwd_ports_eth_addr[];
+
+int l2fwd_node_ctx_add(rte_node_t node_id, union l2fwd_node_ctx *ctx);
+int l2fwd_node_ctx_del(rte_node_t node_id);
+
+int l2fwd_rx_node_init(uint16_t portid, uint16_t nb_rxq);
+rte_node_t l2fwd_get_pkt_fwd_node_id(void);
+rte_node_t l2fwd_get_pkt_tx_node_id(void);
+
+#endif
@@ -29,6 +29,7 @@ all_examples = [
'l2fwd-cat',
'l2fwd-crypto',
'l2fwd-event',
+ 'l2fwd-graph',
'l2fwd-jobstats',
'l2fwd-keepalive',
'l3fwd',