DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH 0/4] app: introduce testgraph application
@ 2023-04-21  6:02 Vamsi Attunuru
  2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
                   ` (4 more replies)
  0 siblings, 5 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-21  6:02 UTC (permalink / raw)
  To: dev, jerinj; +Cc: vattunuru, ndabilpuram

This patch series introduces testgraph application that verifies graph
architecture, it provides an infra to verify the graph & node libraries
and scale the test coverage by adding newer configurations to exercise
various graph topologies & graph-walk models required by the DPDK
applications.

Also this series adds two new nodes (punt_kernel & kernel_recv) to the
node library.

Vamsi Attunuru (4):
  node: add pkt punt to kernel node
  node: add a node to receive pkts from kernel
  node: remove hardcoded node next details
  app: add testgraph application

 app/meson.build                     |    1 +
 app/test-graph/cmdline.c            |  212 +++++
 app/test-graph/cmdline_graph.c      |  297 ++++++
 app/test-graph/cmdline_graph.h      |   19 +
 app/test-graph/meson.build          |   17 +
 app/test-graph/parameters.c         |  157 ++++
 app/test-graph/testgraph.c          | 1309 +++++++++++++++++++++++++++
 app/test-graph/testgraph.h          |   92 ++
 doc/guides/prog_guide/graph_lib.rst |   17 +
 doc/guides/tools/index.rst          |    1 +
 doc/guides/tools/testgraph.rst      |  131 +++
 lib/node/ethdev_rx.c                |    2 -
 lib/node/kernel_recv.c              |  277 ++++++
 lib/node/kernel_recv_priv.h         |   74 ++
 lib/node/meson.build                |    2 +
 lib/node/punt_kernel.c              |  125 +++
 lib/node/punt_kernel_priv.h         |   36 +
 17 files changed, 2767 insertions(+), 2 deletions(-)
 create mode 100644 app/test-graph/cmdline.c
 create mode 100644 app/test-graph/cmdline_graph.c
 create mode 100644 app/test-graph/cmdline_graph.h
 create mode 100644 app/test-graph/meson.build
 create mode 100644 app/test-graph/parameters.c
 create mode 100644 app/test-graph/testgraph.c
 create mode 100644 app/test-graph/testgraph.h
 create mode 100644 doc/guides/tools/testgraph.rst
 create mode 100644 lib/node/kernel_recv.c
 create mode 100644 lib/node/kernel_recv_priv.h
 create mode 100644 lib/node/punt_kernel.c
 create mode 100644 lib/node/punt_kernel_priv.h

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH 1/4] node: add pkt punt to kernel node
  2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
@ 2023-04-21  6:02 ` Vamsi Attunuru
  2023-05-29 17:29   ` Jerin Jacob
  2023-04-21  6:02 ` [PATCH 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-21  6:02 UTC (permalink / raw)
  To: dev, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds a node to punt the packets to kernel over
a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |  10 +++
 lib/node/meson.build                |   1 +
 lib/node/punt_kernel.c              | 125 ++++++++++++++++++++++++++++
 lib/node/punt_kernel_priv.h         |  36 ++++++++
 4 files changed, 172 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index 1cfdc86433..b3b5b14827 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -392,3 +392,13 @@ null
 ~~~~
 This node ignores the set of objects passed to it and reports that all are
 processed.
+
+punt_kernel
+~~~~~~~~~~~
+This node punts the packets to kernel using a raw socket interface. For sending
+the received packets, raw socket uses the packet's destination IP address in
+sockaddr_in address structure and node uses ``sendto`` function to send data
+on the raw socket.
+
+Aftering sending the burst of packets to kernel, this node redirects the same
+objects to pkt_drop node to free up the packet buffers.
diff --git a/lib/node/meson.build b/lib/node/meson.build
index dbdf673c86..48c2da73f7 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'null.c',
         'pkt_cls.c',
         'pkt_drop.c',
+        'punt_kernel.c',
 )
 headers = files('rte_node_ip4_api.h', 'rte_node_eth_api.h')
 # Strict-aliasing rules are violated by uint8_t[] to context size casts.
diff --git a/lib/node/punt_kernel.c b/lib/node/punt_kernel.c
new file mode 100644
index 0000000000..e5dd15b759
--- /dev/null
+++ b/lib/node/punt_kernel.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+
+#include "node_private.h"
+#include "punt_kernel_priv.h"
+
+static __rte_always_inline void
+punt_kernel_process_mbuf(struct rte_node *node, struct rte_mbuf **mbufs, uint16_t cnt)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+	struct sockaddr_in sin = {0};
+	struct rte_ipv4_hdr *ip4;
+	size_t len;
+	char *buf;
+	int i;
+
+	for (i = 0; i < cnt; i++) {
+		ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
+		len = rte_pktmbuf_data_len(mbufs[i]);
+		buf = (char *)ip4;
+
+		sin.sin_family = AF_INET;
+		sin.sin_port = 0;
+		sin.sin_addr.s_addr = ip4->dst_addr;
+
+		if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+			node_err("punt_kernel", "Unable to send packets: %s\n", strerror(errno));
+	}
+}
+
+static uint16_t
+punt_kernel_node_process(struct rte_graph *graph __rte_unused, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
+	uint16_t obj_left = nb_objs;
+
+#define PREFETCH_CNT 4
+
+	while (obj_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *, pkts[7]->l2_len));
+
+		punt_kernel_process_mbuf(node, pkts, PREFETCH_CNT);
+
+		obj_left -= PREFETCH_CNT;
+		pkts += PREFETCH_CNT;
+	}
+
+	while (obj_left > 0) {
+		punt_kernel_process_mbuf(node, pkts, 1);
+
+		obj_left--;
+		pkts++;
+	}
+
+	rte_node_next_stream_move(graph, node, PUNT_KERNEL_NEXT_PKT_DROP);
+
+	return nb_objs;
+}
+
+static int
+punt_kernel_node_init(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+
+	ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (ctx->sock < 0)
+		node_err("punt_kernel", "Unable to open RAW socket\n");
+
+	return 0;
+}
+
+static void
+punt_kernel_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+
+	if (ctx->sock >= 0) {
+		close(ctx->sock);
+		ctx->sock = -1;
+	}
+}
+
+static struct rte_node_register punt_kernel_node_base = {
+	.process = punt_kernel_node_process,
+	.name = "punt_kernel",
+
+	.init = punt_kernel_node_init,
+	.fini = punt_kernel_node_fini,
+
+	.nb_edges = PUNT_KERNEL_NEXT_MAX,
+	.next_nodes = {
+			[PUNT_KERNEL_NEXT_PKT_DROP] = "pkt_drop",
+	},
+};
+
+struct rte_node_register *
+punt_kernel_node_get(void)
+{
+	return &punt_kernel_node_base;
+}
+
+RTE_NODE_REGISTER(punt_kernel_node_base);
diff --git a/lib/node/punt_kernel_priv.h b/lib/node/punt_kernel_priv.h
new file mode 100644
index 0000000000..25ba2072e7
--- /dev/null
+++ b/lib/node/punt_kernel_priv.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __INCLUDE_PUNT_KERNEL_PRIV_H__
+#define __INCLUDE_PUNT_KERNEL_PRIV_H__
+
+struct punt_kernel_node_elem;
+struct punt_kernel_node_ctx;
+typedef struct punt_kernel_node_elem punt_kernel_node_elem_t;
+
+/**
+ * @internal
+ *
+ * PUNT Kernel node context structure.
+ */
+typedef struct punt_kernel_node_ctx {
+	int sock;
+} punt_kernel_node_ctx_t;
+
+enum punt_kernel_next_nodes {
+	PUNT_KERNEL_NEXT_PKT_DROP,
+	PUNT_KERNEL_NEXT_MAX,
+};
+
+/**
+ * @internal
+ *
+ * Get the PUNT Kernel node.
+ *
+ * @return
+ *   Pointer to the PUNT Kernel node.
+ */
+struct rte_node_register *punt_kernel_node_get(void);
+
+#endif /* __INCLUDE_PUNT_KERNEL_PRIV_H__ */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH 2/4] node: add a node to receive pkts from kernel
  2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
  2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
@ 2023-04-21  6:02 ` Vamsi Attunuru
  2023-05-29 17:39   ` Jerin Jacob
  2023-04-21  6:02 ` [PATCH 3/4] node: remove hardcoded node next details Vamsi Attunuru
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-21  6:02 UTC (permalink / raw)
  To: dev, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds a node to receive packets from kernel
over a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |   7 +
 lib/node/kernel_recv.c              | 277 ++++++++++++++++++++++++++++
 lib/node/kernel_recv_priv.h         |  74 ++++++++
 lib/node/meson.build                |   1 +
 4 files changed, 359 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index b3b5b14827..1057f16de8 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -402,3 +402,10 @@ on the raw socket.
 
 Aftering sending the burst of packets to kernel, this node redirects the same
 objects to pkt_drop node to free up the packet buffers.
+
+kernel_recv
+~~~~~~~~~~~
+This node receives packets from kernel over a raw socket interface. Uses ``poll``
+function to poll on the socket fd for ``POLLIN`` events to read the packets from
+raw socket to stream buffer and does ``rte_node_next_stream_move()`` when there
+are received packets.
diff --git a/lib/node/kernel_recv.c b/lib/node/kernel_recv.c
new file mode 100644
index 0000000000..361dcc3b5f
--- /dev/null
+++ b/lib/node/kernel_recv.c
@@ -0,0 +1,277 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+#include <rte_malloc.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_net.h>
+
+#include "ethdev_rx_priv.h"
+#include "kernel_recv_priv.h"
+#include "node_private.h"
+
+static struct kernel_recv_node_main kernel_recv_main;
+
+static inline struct rte_mbuf *
+alloc_rx_mbuf(kernel_recv_node_ctx_t *ctx)
+{
+	kernel_recv_info_t *rx = ctx->recv_info;
+
+	if (rx->idx >= rx->cnt) {
+		uint16_t cnt;
+
+		rx->idx = 0;
+		rx->cnt = 0;
+
+		cnt = rte_pktmbuf_alloc_bulk(ctx->pktmbuf_pool, rx->rx_bufs, KERN_RECV_CACHE_COUNT);
+		if (cnt <= 0)
+			return NULL;
+
+		rx->cnt = cnt;
+	}
+
+	return rx->rx_bufs[rx->idx++];
+}
+
+static inline void
+mbuf_update(struct rte_mbuf **mbufs, uint16_t nb_pkts)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	int i;
+
+	for (i = 0; i < nb_pkts; i++) {
+		m = mbufs[i];
+
+		m->packet_type = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+		m->ol_flags = 0;
+		m->tx_offload = 0;
+
+		m->l2_len = hdr_lens.l2_len;
+		m->l3_len = hdr_lens.l3_len;
+		m->l4_len = hdr_lens.l4_len;
+	}
+}
+
+static uint16_t
+recv_pkt_parse(void **objs, uint16_t nb_pkts)
+{
+	uint16_t pkts_left = nb_pkts;
+	struct rte_mbuf **pkts;
+	int i;
+
+	pkts = (struct rte_mbuf **)objs;
+
+	if (pkts_left >= 4) {
+		for (i = 0; i < 4; i++)
+			rte_prefetch0(rte_pktmbuf_mtod(pkts[i], void *));
+	}
+
+	while (pkts_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[4], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[5], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[6], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[7], void *));
+
+		/* Extract ptype of mbufs */
+		mbuf_update(pkts, 4);
+
+		pkts += 4;
+		pkts_left -= 4;
+	}
+
+	if (pkts_left > 0)
+		mbuf_update(pkts, pkts_left);
+
+	return nb_pkts;
+}
+
+static uint16_t
+kernel_recv_node_do(struct rte_graph *graph, struct rte_node *node, kernel_recv_node_ctx_t *ctx)
+{
+	kernel_recv_info_t *rx;
+	uint16_t next_index;
+	int fd;
+
+	rx = ctx->recv_info;
+	next_index = rx->cls_next;
+
+	fd = rx->sock;
+	if (fd > 0) {
+		struct rte_mbuf **mbufs;
+		uint16_t len = 0, count = 0;
+		int nb_cnt, i;
+
+		nb_cnt = (node->size >= RTE_GRAPH_BURST_SIZE) ? RTE_GRAPH_BURST_SIZE : node->size;
+
+		mbufs = (struct rte_mbuf **)node->objs;
+		for (i = 0; i < nb_cnt; i++) {
+			struct rte_mbuf *m = alloc_rx_mbuf(ctx);
+
+			if (!m)
+				break;
+
+			len = read(fd, rte_pktmbuf_mtod(m, char *), rte_pktmbuf_tailroom(m));
+			if (len == 0 || len == 0xFFFF) {
+				rte_pktmbuf_free(m);
+				if (rx->idx <= 0)
+					node_dbg("kernel_recv", "rx_mbuf array is empty\n");
+				rx->idx--;
+				break;
+			}
+			*mbufs++ = m;
+
+			m->port = node->id;
+			rte_pktmbuf_data_len(m) = len;
+
+			count++;
+		}
+
+		if (count) {
+			recv_pkt_parse(node->objs, count);
+			node->idx = count;
+
+			/* Enqueue to next node */
+			rte_node_next_stream_move(graph, node, next_index);
+		}
+
+		return count;
+	}
+
+	return 0;
+}
+
+static uint16_t
+kernel_recv_node_process(struct rte_graph *graph, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+	int fd;
+
+	RTE_SET_USED(objs);
+	RTE_SET_USED(nb_objs);
+
+	if (!ctx)
+		return 0;
+
+	fd = ctx->recv_info->sock;
+	if (fd > 0) {
+		struct pollfd fds = {.fd = fd, .events = POLLIN};
+
+		if (poll(&fds, 1, 0) > 0) {
+			if (fds.revents & POLLIN)
+				return kernel_recv_node_do(graph, node, ctx);
+		}
+	}
+
+	return 0;
+}
+
+static int
+kernel_recv_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+	kernel_recv_node_elem_t *elem = kernel_recv_main.head;
+	kernel_recv_info_t *recv_info;
+	int sock;
+
+	while (elem) {
+		if (elem->nid == node->id) {
+			/* Update node specific context */
+			memcpy(ctx, &elem->ctx, sizeof(kernel_recv_node_ctx_t));
+			break;
+		}
+		elem = elem->next;
+	}
+
+	RTE_VERIFY(elem != NULL);
+
+	if (ctx->pktmbuf_pool == NULL) {
+		node_err("kernel_recv", "Invalid mbuf pool on graph %s\n", graph->name);
+		return -EINVAL;
+	}
+
+	recv_info =
+		rte_zmalloc("kernel_recv_info", sizeof(kernel_recv_info_t), RTE_CACHE_LINE_SIZE);
+	if (!recv_info) {
+		node_err("kernel_recv", "Kernel recv_info is NULL\n");
+		return -ENOMEM;
+	}
+
+	sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (sock < 0) {
+		node_err("kernel_recv", "Unable to open RAW socket\n");
+		return sock;
+	}
+
+	recv_info->cls_next = ETHDEV_RX_NEXT_PKT_CLS;
+	recv_info->sock = sock;
+	ctx->recv_info = recv_info;
+
+	return 0;
+}
+
+static void
+kernel_recv_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+
+	if (ctx->recv_info) {
+		close(ctx->recv_info->sock);
+		ctx->recv_info->sock = -1;
+		rte_free(ctx->recv_info);
+	}
+
+	ctx->recv_info = NULL;
+}
+
+struct kernel_recv_node_main *
+kernel_recv_node_data_get(void)
+{
+	return &kernel_recv_main;
+}
+
+static struct rte_node_register kernel_recv_node_base = {
+	.process = kernel_recv_node_process,
+	.flags = RTE_NODE_SOURCE_F,
+	.name = "kernel_recv",
+
+	.init = kernel_recv_node_init,
+	.fini = kernel_recv_node_fini,
+
+	.nb_edges = KERNEL_RECV_NEXT_MAX,
+	.next_nodes = {
+			/* Default pkt classification node */
+			[KERNEL_RECV_NEXT_PKT_CLS] = "pkt_cls",
+			[KERNEL_RECV_NEXT_IP4_LOOKUP] = "ip4_lookup",
+	},
+};
+
+struct rte_node_register *
+kernel_recv_node_get(void)
+{
+	return &kernel_recv_node_base;
+}
+
+RTE_NODE_REGISTER(kernel_recv_node_base);
diff --git a/lib/node/kernel_recv_priv.h b/lib/node/kernel_recv_priv.h
new file mode 100644
index 0000000000..c2bfbf40bd
--- /dev/null
+++ b/lib/node/kernel_recv_priv.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __INCLUDE_KERNEL_RECV_PRIV_H__
+#define __INCLUDE_KERNEL_RECV_PRIV_H__
+
+#define KERN_RECV_CACHE_COUNT 64
+
+typedef struct kernel_recv_info {
+	struct rte_mbuf *rx_bufs[KERN_RECV_CACHE_COUNT];
+	uint16_t cls_next;
+	uint16_t idx;
+	uint16_t cnt;
+	int sock;
+} kernel_recv_info_t;
+
+/**
+ * @internal
+ *
+ * Kernel Recv node context structure.
+ */
+typedef struct kernel_recv_node_ctx {
+	struct rte_mempool *pktmbuf_pool;
+	kernel_recv_info_t *recv_info;
+} kernel_recv_node_ctx_t;
+
+/**
+ * @internal
+ *
+ * Kernel Recv node list element structure.
+ */
+typedef struct kernel_recv_node_elem {
+	struct kernel_recv_node_elem *next; /**< Pointer to the next node element. */
+	struct kernel_recv_node_ctx ctx;    /**< Kernel Recv node context. */
+	rte_node_t nid;			    /**< Node identifier of the Kernel Recv node. */
+} kernel_recv_node_elem_t;
+
+enum kernel_recv_next_nodes {
+	KERNEL_RECV_NEXT_IP4_LOOKUP,
+	KERNEL_RECV_NEXT_PKT_CLS,
+	KERNEL_RECV_NEXT_MAX,
+};
+
+/**
+ * @internal
+ *
+ * Kernel Recv node main structure.
+ */
+struct kernel_recv_node_main {
+	kernel_recv_node_elem_t *head; /**< Pointer to the head node element. */
+};
+
+/**
+ * @internal
+ *
+ * Get the Kernel Recv node data.
+ *
+ * @return
+ *   Pointer to Kernel Recv node data.
+ */
+struct kernel_recv_node_main *kernel_recv_node_data_get(void);
+
+/**
+ * @internal
+ *
+ * Get the Kernel Recv node.
+ *
+ * @return
+ *   Pointer to the Kernel Recv node.
+ */
+struct rte_node_register *kernel_recv_node_get(void);
+
+#endif /* __INCLUDE_KERNEL_RECV_PRIV_H__ */
diff --git a/lib/node/meson.build b/lib/node/meson.build
index 48c2da73f7..8b1b024f61 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'ethdev_tx.c',
         'ip4_lookup.c',
         'ip4_rewrite.c',
+        'kernel_recv.c',
         'log.c',
         'null.c',
         'pkt_cls.c',
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH 3/4] node: remove hardcoded node next details
  2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
  2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
  2023-04-21  6:02 ` [PATCH 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
@ 2023-04-21  6:02 ` Vamsi Attunuru
  2023-04-21  6:02 ` [PATCH 4/4] app: add testgraph application Vamsi Attunuru
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
  4 siblings, 0 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-21  6:02 UTC (permalink / raw)
  To: dev, jerinj; +Cc: vattunuru, ndabilpuram

For ethdev_rx node, node_next details can be populated
during node cloning time and same gets assigned to
node context structure during node initialization.

Patch removes overriding node_next details in node
init().

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 lib/node/ethdev_rx.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lib/node/ethdev_rx.c b/lib/node/ethdev_rx.c
index a19237b42f..85816c489c 100644
--- a/lib/node/ethdev_rx.c
+++ b/lib/node/ethdev_rx.c
@@ -194,8 +194,6 @@ ethdev_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
 
 	RTE_VERIFY(elem != NULL);
 
-	ctx->cls_next = ETHDEV_RX_NEXT_PKT_CLS;
-
 	/* Check and setup ptype */
 	return ethdev_ptype_setup(ctx->port_id, ctx->queue_id);
 }
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH 4/4] app: add testgraph application
  2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
                   ` (2 preceding siblings ...)
  2023-04-21  6:02 ` [PATCH 3/4] node: remove hardcoded node next details Vamsi Attunuru
@ 2023-04-21  6:02 ` Vamsi Attunuru
  2023-05-09  6:09   ` Jerin Jacob
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-21  6:02 UTC (permalink / raw)
  To: dev, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds test-graph application to validate graph
and node libraries.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 app/meson.build                |    1 +
 app/test-graph/cmdline.c       |  212 ++++++
 app/test-graph/cmdline_graph.c |  297 ++++++++
 app/test-graph/cmdline_graph.h |   19 +
 app/test-graph/meson.build     |   17 +
 app/test-graph/parameters.c    |  157 ++++
 app/test-graph/testgraph.c     | 1309 ++++++++++++++++++++++++++++++++
 app/test-graph/testgraph.h     |   92 +++
 doc/guides/tools/index.rst     |    1 +
 doc/guides/tools/testgraph.rst |  131 ++++
 10 files changed, 2236 insertions(+)

diff --git a/app/meson.build b/app/meson.build
index 74d2420f67..6c7b24e604 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -22,6 +22,7 @@ apps = [
         'test-eventdev',
         'test-fib',
         'test-flow-perf',
+        'test-graph',
         'test-gpudev',
         'test-mldev',
         'test-pipeline',
diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
new file mode 100644
index 0000000000..a07a8a24f9
--- /dev/null
+++ b/app/test-graph/cmdline.c
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <stdlib.h>
+
+#include <cmdline.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_etheraddr.h>
+#include <cmdline_parse_ipaddr.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_rdline.h>
+#include <cmdline_socket.h>
+
+#include "cmdline_graph.h"
+#include "testgraph.h"
+
+static struct cmdline *testgraph_cl;
+static cmdline_parse_ctx_t *main_ctx;
+
+/* *** Help command with introduction. *** */
+struct cmd_help_brief_result {
+	cmdline_fixed_string_t help;
+};
+
+static void
+cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	cmdline_printf(cl,
+		       "\n"
+		       "Help is available for the following sections:\n\n"
+		       "    help control                    : Start and stop graph walk.\n"
+		       "    help display                    : Displaying port, stats and config "
+		       "information.\n"
+		       "    help config                     : Configuration information.\n"
+		       "    help all                        : All of the above sections.\n\n");
+}
+
+static cmdline_parse_token_string_t cmd_help_brief_help =
+	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help, "help");
+
+static cmdline_parse_inst_t cmd_help_brief = {
+	.f = cmd_help_brief_parsed,
+	.data = NULL,
+	.help_str = "help: Show help",
+	.tokens = {
+			(void *)&cmd_help_brief_help,
+			NULL,
+		},
+};
+
+/* *** Help command with help sections. *** */
+struct cmd_help_long_result {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t section;
+};
+
+static void
+cmd_help_long_parsed(void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	int show_all = 0;
+	struct cmd_help_long_result *res = parsed_result;
+
+	if (!strcmp(res->section, "all"))
+		show_all = 1;
+
+	if (show_all || !strcmp(res->section, "control")) {
+
+		cmdline_printf(cl, "\n"
+				   "Control forwarding:\n"
+				   "-------------------\n\n"
+
+				   "start graph_walk\n"
+				   " Start graph_walk on worker threads.\n\n"
+
+				   "stop graph_walk\n"
+				   " Stop worker threads from running graph_walk.\n\n"
+
+				   "quit\n"
+				   "    Quit to prompt.\n\n");
+	}
+
+	if (show_all || !strcmp(res->section, "display")) {
+
+		cmdline_printf(cl,
+			       "\n"
+			       "Display:\n"
+			       "--------\n\n"
+
+			       "show node_list\n"
+			       " Display the list of supported nodes.\n\n"
+
+			       "show graph_stats\n"
+			       " Display the node statistics of graph cluster.\n\n");
+	}
+
+	if (show_all || !strcmp(res->section, "config")) {
+		cmdline_printf(cl, "\n"
+				   "Configuration:\n"
+				   "--------------\n"
+				   "set lcore_config (port_id0,rxq0,lcore_idX),..."
+				   ".....,(port_idX,rxqX,lcoreidY)\n"
+				   " Set lcore configuration.\n\n"
+
+				   "create_graph (node0_name,node1_name,...,nodeX_name)\n"
+				   " Create graph instances using the provided node details.\n\n"
+
+				   "destroy_graph\n"
+				   " Destroy the graph instances.\n\n");
+	}
+}
+
+static cmdline_parse_token_string_t cmd_help_long_help =
+	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help, "help");
+
+static cmdline_parse_token_string_t cmd_help_long_section = TOKEN_STRING_INITIALIZER(
+	struct cmd_help_long_result, section, "all#control#display#config");
+
+static cmdline_parse_inst_t cmd_help_long = {
+	.f = cmd_help_long_parsed,
+	.data = NULL,
+	.help_str = "help all|control|display|config: "
+		    "Show help",
+	.tokens = {
+			(void *)&cmd_help_long_help,
+			(void *)&cmd_help_long_section,
+			NULL,
+		},
+};
+
+/* *** QUIT *** */
+struct cmd_quit_result {
+	cmdline_fixed_string_t quit;
+};
+
+static void
+cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	cmdline_quit(cl);
+	cl_quit = 1;
+}
+
+static cmdline_parse_token_string_t cmd_quit_quit =
+	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
+
+static cmdline_parse_inst_t cmd_quit = {
+	.f = cmd_quit_parsed,
+	.data = NULL,
+	.help_str = "quit: Exit application",
+	.tokens = {
+			(void *)&cmd_quit_quit,
+			NULL,
+		},
+};
+
+/* list of instructions */
+static cmdline_parse_ctx_t builtin_ctx[] = {
+	(cmdline_parse_inst_t *)&cmd_help_brief,
+	(cmdline_parse_inst_t *)&cmd_help_long,
+	(cmdline_parse_inst_t *)&cmd_quit,
+	(cmdline_parse_inst_t *)&cmd_show_node_list,
+	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
+	(cmdline_parse_inst_t *)&cmd_create_graph,
+	(cmdline_parse_inst_t *)&cmd_destroy_graph,
+	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
+	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
+	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
+	NULL,
+};
+
+int
+init_cmdline(void)
+{
+	unsigned int count;
+	unsigned int i;
+
+	count = 0;
+	for (i = 0; builtin_ctx[i] != NULL; i++)
+		count++;
+
+	/* cmdline expects a NULL terminated array */
+	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
+	if (main_ctx == NULL)
+		return -1;
+
+	count = 0;
+	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
+		main_ctx[count] = builtin_ctx[i];
+
+	return 0;
+}
+
+void
+prompt_exit(void)
+{
+	cmdline_quit(testgraph_cl);
+}
+
+/* prompt function, called from main on MAIN lcore */
+void
+prompt(void)
+{
+	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
+	if (testgraph_cl == NULL) {
+		fprintf(stderr, "Failed to create stdin based cmdline context\n");
+		return;
+	}
+
+	cmdline_interact(testgraph_cl);
+	cmdline_stdin_exit(testgraph_cl);
+}
diff --git a/app/test-graph/cmdline_graph.c b/app/test-graph/cmdline_graph.c
new file mode 100644
index 0000000000..1c9fd94355
--- /dev/null
+++ b/app/test-graph/cmdline_graph.c
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+
+#include "cmdline_graph.h"
+#include "testgraph.h"
+
+/* *** Show supported node details *** */
+struct cmd_show_node_list_result {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t node_list;
+};
+
+static cmdline_parse_token_string_t cmd_show_node_list_show =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, show, "show");
+static cmdline_parse_token_string_t cmd_show_node_list_node_list =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, node_list, "node_list");
+
+static void
+cmd_show_node_list_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			  __rte_unused void *data)
+{
+	rte_node_t node_cnt = rte_node_max_count();
+	rte_node_t id;
+
+	printf("\n**** Supported Graph Nodes ****\n");
+	for (id = 0; id < node_cnt; id++)
+		printf("%s\n", rte_node_id_to_name(id));
+
+	printf("********************************\n");
+}
+
+cmdline_parse_inst_t cmd_show_node_list = {
+	.f = cmd_show_node_list_parsed,
+	.data = NULL,
+	.help_str = "show node_list",
+	.tokens = {
+			(void *)&cmd_show_node_list_show,
+			(void *)&cmd_show_node_list_node_list,
+			NULL,
+		},
+};
+
+/* *** Set lcore config *** */
+struct cmd_set_lcore_config_result {
+	cmdline_fixed_string_t set;
+	cmdline_fixed_string_t lcore_config;
+	cmdline_multi_string_t token_string;
+};
+
+static cmdline_parse_token_string_t cmd_set_lcore_config_set =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set, "set");
+static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, lcore_config, "lcore_config");
+static cmdline_parse_token_string_t cmd_set_lcore_config_token_string = TOKEN_STRING_INITIALIZER(
+	struct cmd_set_lcore_config_result, token_string, TOKEN_STRING_MULTI);
+
+static void
+cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	struct cmd_set_lcore_config_result *res = parsed_result;
+	const char *t_str = res->token_string;
+	int ret;
+
+	/* Parse string */
+	ret = parse_config(t_str);
+	if (ret) {
+		printf(" lcore_config string parse error\n");
+		return;
+	}
+
+	validate_config();
+}
+
+cmdline_parse_inst_t cmd_set_lcore_config = {
+	.f = cmd_set_lcore_config_parsed,
+	.data = NULL,
+	.help_str = "set lcore_config "
+		    "(port,queue,lcore),[(port,queue,lcore) ... (port,queue,lcore)]",
+	.tokens = {
+			(void *)&cmd_set_lcore_config_set,
+			(void *)&cmd_set_lcore_config_lcore_config,
+			(void *)&cmd_set_lcore_config_token_string,
+			NULL,
+		},
+};
+
+/* *** Create graph *** */
+struct cmd_create_graph_result {
+	cmdline_fixed_string_t create_graph;
+	cmdline_multi_string_t token_string;
+};
+
+static cmdline_parse_token_string_t cmd_create_graph_create_graph =
+	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, create_graph, "create_graph");
+static cmdline_parse_token_string_t cmd_create_graph_token_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, token_string, TOKEN_STRING_MULTI);
+
+static void
+cmd_create_graph_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
+			__rte_unused void *data)
+{
+	struct cmd_create_graph_result *res = parsed_result;
+	const char *t_str = res->token_string;
+	uint64_t valid_nodes = 0;
+	int ret;
+
+	ret = parse_node_patterns(t_str);
+	if (ret) {
+		printf("parse_node_patterns failed\n");
+		cleanup_node_pattern();
+		return;
+	}
+
+	ret = validate_node_names(&valid_nodes);
+	if (ret) {
+		printf("validate_node_names() failed\n");
+		cleanup_node_pattern();
+		return;
+	}
+
+	nb_conf = ethdev_ports_setup();
+
+	ethdev_rxq_configure();
+	ethdev_txq_configure();
+
+	ret = configure_graph_nodes(valid_nodes);
+	if (ret) {
+		printf("configure_graph_nodes() failed\n");
+		cleanup_node_pattern();
+		return;
+	}
+
+	start_eth_ports();
+	check_all_ports_link_status(enabled_port_mask);
+
+	ret = create_graph(node_pattern, num_patterns);
+	if (ret)
+		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
+
+	stats = create_graph_cluster_stats();
+	if (stats == NULL)
+		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
+}
+
+cmdline_parse_inst_t cmd_create_graph = {
+	.f = cmd_create_graph_parsed,
+	.data = NULL,
+	.help_str = "create_graph "
+		    "[node_name0,node_name1,node_name2 ... node_nameX]",
+	.tokens = {
+			(void *)&cmd_create_graph_create_graph,
+			(void *)&cmd_create_graph_token_string,
+			NULL,
+		},
+};
+
+/**** Destroy graph ****/
+struct cmd_destroy_graph_result {
+	cmdline_fixed_string_t destroy_graph;
+};
+
+static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =
+	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result, destroy_graph, "destroy_graph");
+
+static void
+cmd_destroy_graph_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			 __rte_unused void *data)
+{
+	uint32_t lcore_id;
+
+	run_graph_walk = false;
+	graph_walk_quit = true;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+		rte_eal_wait_lcore(lcore_id);
+
+	destroy_graph();
+	stop_eth_ports();
+}
+
+cmdline_parse_inst_t cmd_destroy_graph = {
+	.f = cmd_destroy_graph_parsed,
+	.data = NULL,
+	.help_str = "destroy_graph",
+	.tokens = {
+			(void *)&cmd_destroy_graph_destroy_graph,
+			NULL,
+		},
+};
+
+/**** Start graph_walk ****/
+struct cmd_start_graph_walk_result {
+	cmdline_fixed_string_t start;
+	cmdline_fixed_string_t graph_walk;
+};
+
+static cmdline_parse_token_string_t cmd_start_graph_walk_start =
+	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, start, "start");
+static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =
+	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, graph_walk, "graph_walk");
+
+static void
+cmd_start_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	static bool launch_graph_walk;
+
+	if (!launch_graph_walk) {
+		rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
+		launch_graph_walk = true;
+	}
+
+	run_graph_walk = true;
+}
+
+cmdline_parse_inst_t cmd_start_graph_walk = {
+	.f = cmd_start_graph_walk_parsed,
+	.data = NULL,
+	.help_str = "start graph_walk",
+	.tokens = {
+			(void *)&cmd_start_graph_walk_start,
+			(void *)&cmd_start_graph_walk_graph_walk,
+			NULL,
+		},
+};
+
+/**** Stop graph_walk ****/
+struct cmd_stop_graph_walk_result {
+	cmdline_fixed_string_t stop;
+	cmdline_fixed_string_t graph_walk;
+};
+
+static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
+	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, stop, "stop");
+static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =
+	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, graph_walk, "graph_walk");
+
+static void
+cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			   __rte_unused void *data)
+{
+	run_graph_walk = false;
+}
+
+cmdline_parse_inst_t cmd_stop_graph_walk = {
+	.f = cmd_stop_graph_walk_parsed,
+	.data = NULL,
+	.help_str = "stop graph_walk",
+	.tokens = {
+			(void *)&cmd_stop_graph_walk_stop,
+			(void *)&cmd_stop_graph_walk_graph_walk,
+			NULL,
+		},
+};
+
+/**** Show graph_stats ****/
+struct cmd_show_graph_stats_result {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph_stats;
+};
+
+static cmdline_parse_token_string_t cmd_show_graph_stats_show =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, show, "show");
+static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, graph_stats, "graph_stats");
+
+static void
+cmd_show_graph_stats_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	if (rte_graph_has_stats_feature()) {
+		if (stats)
+			rte_graph_cluster_stats_get(stats, 0);
+	} else {
+		printf(" graph stats feature not enabled in rte_config.\n");
+	}
+}
+
+cmdline_parse_inst_t cmd_show_graph_stats = {
+	.f = cmd_show_graph_stats_parsed,
+	.data = NULL,
+	.help_str = "show graph_stats",
+	.tokens = {
+			(void *)&cmd_show_graph_stats_show,
+			(void *)&cmd_show_graph_stats_graph_stats,
+			NULL,
+		},
+};
diff --git a/app/test-graph/cmdline_graph.h b/app/test-graph/cmdline_graph.h
new file mode 100644
index 0000000000..2846ff5425
--- /dev/null
+++ b/app/test-graph/cmdline_graph.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef _CMDLINE_GRAPH_H_
+#define _CMDLINE_GRAPH_H_
+
+extern cmdline_parse_inst_t cmd_show_node_list;
+extern cmdline_parse_inst_t cmd_set_lcore_config;
+
+extern cmdline_parse_inst_t cmd_create_graph;
+extern cmdline_parse_inst_t cmd_destroy_graph;
+
+extern cmdline_parse_inst_t cmd_start_graph_walk;
+extern cmdline_parse_inst_t cmd_stop_graph_walk;
+
+extern cmdline_parse_inst_t cmd_show_graph_stats;
+
+#endif /* _CMDLINE_GRAPH_H_ */
diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
new file mode 100644
index 0000000000..a969a91183
--- /dev/null
+++ b/app/test-graph/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(C) 2023 Marvell International Ltd.
+
+# override default name to drop the hyphen
+name = 'test-graph'
+cflags += '-Wno-deprecated-declarations'
+sources = files(
+        'cmdline.c',
+        'cmdline_graph.c',
+        'parameters.c',
+        'testgraph.c',
+)
+
+deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
+
+# Driver-specific commands are located in driver directories.
+includes = include_directories('.')
diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
new file mode 100644
index 0000000000..b990ca4a1c
--- /dev/null
+++ b/app/test-graph/parameters.c
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <getopt.h>
+#include <stdlib.h>
+
+#include "testgraph.h"
+
+static const char short_options[] = "p:" /* portmask */
+				    "P"	 /* promiscuous */
+				    "i"	 /* interactive */
+	;
+
+#define CMD_LINE_OPT_CONFIG	   "config"
+#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
+#define CMD_LINE_OPT_INTERACTIVE   "interactive"
+#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
+#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
+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_CONFIG_NUM,
+	CMD_LINE_OPT_NODE_PATTERN_NUM,
+	CMD_LINE_OPT_INTERACTIVE_NUM,
+	CMD_LINE_OPT_NO_NUMA_NUM,
+	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
+};
+
+static const struct option lgopts[] = {
+	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
+	{CMD_LINE_OPT_NODE_PATTERN, 1, 0, CMD_LINE_OPT_NODE_PATTERN_NUM},
+	{CMD_LINE_OPT_INTERACTIVE, 0, 0, CMD_LINE_OPT_INTERACTIVE_NUM},
+	{CMD_LINE_OPT_NO_NUMA, 0, 0, CMD_LINE_OPT_NO_NUMA_NUM},
+	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0, CMD_LINE_OPT_PARSE_PER_PORT_POOL},
+	{NULL, 0, 0, 0},
+};
+
+/* Display usage */
+static void
+print_usage(const char *prgname)
+{
+	fprintf(stderr,
+		"%s [EAL options] --"
+		" -p PORTMASK"
+		" [-P]"
+		" [-i]"
+		" --config (port,queue,lcore)[,(port,queue,lcore)]"
+		" --node-pattern (node_name0,node_name1[,node_nameX)]"
+		" [--no-numa]"
+		" [--per-port-pool]"
+		" [--interactive]"
+
+		"  -p PORTMASK: Hexadecimal bitmask of ports to configure\n"
+		"  -P : Enable promiscuous mode\n"
+		"  -i : Enter interactive mode\n"
+		"  --config (port,queue,lcore): Rx queue configuration\n"
+		"  --node-pattern (node_names): node patterns to create graph\n"
+		"  --no-numa: Disable numa awareness\n"
+		"  --per-port-pool: Use separate buffer pool per port\n"
+		"  --interactive: Enter interactive mode\n",
+		prgname);
+}
+
+static int
+parse_portmask(const char *portmask)
+{
+	char *end = NULL;
+	unsigned long pm;
+
+	/* Parse hexadecimal string */
+	pm = strtoul(portmask, &end, 16);
+	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
+		return 0;
+
+	return pm;
+}
+
+/* Parse the argument given in the command line of the application */
+int
+parse_cmdline_args(int argc, char **argv)
+{
+	char *prgname = argv[0];
+	int option_index;
+	char **argvopt;
+	int opt, ret;
+
+	argvopt = argv;
+
+	/* Error or normal output strings. */
+	while ((opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index)) != EOF) {
+
+		switch (opt) {
+		/* Portmask */
+		case 'p':
+			enabled_port_mask = parse_portmask(optarg);
+			if (enabled_port_mask == 0) {
+				fprintf(stderr, "Invalid portmask\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+
+		case 'P':
+			promiscuous_on = 1;
+			break;
+
+		/* Long options */
+		case CMD_LINE_OPT_CONFIG_NUM:
+			ret = parse_config(optarg);
+			if (ret) {
+				fprintf(stderr, "Invalid config\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+		case CMD_LINE_OPT_NODE_PATTERN_NUM:
+			ret = parse_node_patterns(optarg);
+			if (ret) {
+				fprintf(stderr, "Invalid node_patterns\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+
+		case CMD_LINE_OPT_INTERACTIVE_NUM:
+		case 'i':
+			printf("Interactive-mode selected\n");
+			interactive = 1;
+			break;
+
+		case CMD_LINE_OPT_NO_NUMA_NUM:
+			numa_on = 0;
+			break;
+
+		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
+			printf("Per port buffer pool is enabled\n");
+			per_port_pool = 1;
+			break;
+
+		default:
+			print_usage(prgname);
+			return -1;
+		}
+	}
+
+	if (optind >= 0)
+		argv[optind - 1] = prgname;
+	ret = optind - 1;
+	optind = 1; /* Reset getopt lib */
+
+	return ret;
+}
diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
new file mode 100644
index 0000000000..b236b907c7
--- /dev/null
+++ b/app/test-graph/testgraph.c
@@ -0,0 +1,1309 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fnmatch.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include <rte_bus.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_eal.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_graph_worker.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_mempool.h>
+#include <rte_per_lcore.h>
+#include <ethdev_rx_priv.h>
+#include <ethdev_tx_priv.h>
+#include <punt_kernel_priv.h>
+
+#include "testgraph.h"
+
+/* Log type */
+#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
+
+/*
+ * Configurable number of RX/TX ring descriptors
+ */
+#define RX_DESC_DEFAULT 1024
+#define TX_DESC_DEFAULT 1024
+
+#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
+#define MAX_RX_QUEUE_PER_PORT 128
+
+#define MAX_RX_QUEUE_PER_LCORE 16
+
+#define NB_SOCKETS 8
+
+/* Static global variables used within this file. */
+uint16_t nb_rxd = RX_DESC_DEFAULT;
+uint16_t nb_txd = TX_DESC_DEFAULT;
+
+static volatile bool force_quit;
+volatile bool graph_walk_quit;
+
+volatile bool run_graph_walk = true;
+
+uint8_t cl_quit;
+
+uint8_t interactive; /**< interactive mode is off by default */
+
+const char **node_patterns;
+
+char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};
+uint8_t num_patterns;
+
+int promiscuous_on; /**< Ports set in promiscuous mode off by default. */
+
+int numa_on = 1;   /**< NUMA is enabled by default. */
+int per_port_pool; /**< Use separate buffer pools per port; disabled */
+		   /**< by default */
+
+int testgraph_logtype; /**< Log type for testpmd logs */
+
+struct rte_graph_cluster_stats *stats;
+
+/* Mask of enabled ports */
+uint32_t enabled_port_mask;
+
+uint32_t nb_conf;
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_tx_queue {
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+/* Lcore conf */
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
+	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
+
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+
+struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
+static struct lcore_params lcore_params_array_default[] = {
+	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
+	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
+};
+
+struct lcore_params *lcore_params = lcore_params_array_default;
+uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
+
+static struct rte_eth_conf port_conf = {
+	.rxmode = {
+			.mq_mode = RTE_ETH_MQ_RX_RSS,
+		},
+	.rx_adv_conf = {
+			.rss_conf = {
+					.rss_key = NULL,
+					.rss_hf = RTE_ETH_RSS_IP,
+				},
+		},
+	.txmode = {
+			.mq_mode = RTE_ETH_MQ_TX_NONE,
+		},
+};
+
+static const struct node_list test_node_list[] = {{{"ethdev_rx", "ethdev_tx"}, 0, 2},
+						{{"ethdev_rx", "punt_kernel"}, 0, 2} };
+
+static const struct node_list supported_nodes[] = {{{"ethdev_rx"}, TEST_GRAPH_ETHDEV_RX_NODE, 1},
+						{{"ethdev_tx"}, TEST_GRAPH_ETHDEV_TX_NODE, 1},
+						{{"punt_kernel"}, TEST_GRAPH_PUNT_KERNEL_NODE, 1},
+						{{"kernel_recv"}, TEST_GRAPH_KERNEL_RECV_NODE, 1},
+						{{"ip4_lookup"}, TEST_GRAPH_IP4_LOOKUP_NODE, 1},
+						{{"ip4_rewrite"}, TEST_GRAPH_IP4_REWRITE_NODE, 1},
+						{{"pkt_cls"}, TEST_GRAPH_PKT_CLS_NODE, 1},
+						{{"pkt_drop"}, TEST_GRAPH_PKT_DROP_NODE, 1},
+						{{"NULL"}, TEST_GRAPH_NULL_NODE, 1} };
+
+static struct rte_mempool *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
+
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+
+static int
+check_lcore_params(void)
+{
+	uint8_t queue, lcore;
+	int socketid;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		queue = lcore_params[i].queue_id;
+		if (queue >= MAX_RX_QUEUE_PER_PORT) {
+			printf("Invalid queue number: %hhu\n", queue);
+			return -1;
+		}
+		lcore = lcore_params[i].lcore_id;
+		if (!rte_lcore_is_enabled(lcore)) {
+			printf("Error: lcore %hhu is not enabled in lcore mask\n", lcore);
+			return -1;
+		}
+
+		if (lcore == rte_get_main_lcore()) {
+			printf("Error: lcore %u is main lcore\n", lcore);
+			return -1;
+		}
+		socketid = rte_lcore_to_socket_id(lcore);
+		if ((socketid != 0) && (numa_on == 0)) {
+			printf("Warning: lcore %hhu is on socket %d with numa off\n", lcore,
+			       socketid);
+		}
+	}
+
+	return 0;
+}
+
+static int
+check_port_config(void)
+{
+	uint16_t portid;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		portid = lcore_params[i].port_id;
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("Port %u is not enabled in port mask\n", portid);
+			return -1;
+		}
+		if (!rte_eth_dev_is_valid_port(portid)) {
+			printf("Port %u is not present on the board\n", portid);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static uint8_t
+get_port_n_rx_queues(const uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+init_lcore_rx_queues(void)
+{
+	uint16_t i, nb_rx_queue;
+	uint8_t lcore;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		lcore = lcore_params[i].lcore_id;
+		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
+		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
+			printf("Error: too many queues (%u) for lcore: %u\n",
+			       (unsigned int)nb_rx_queue + 1, (unsigned int)lcore);
+			return -1;
+		}
+
+		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id = lcore_params[i].port_id;
+		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id = lcore_params[i].queue_id;
+		lcore_conf[lcore].n_rx_queue++;
+	}
+
+	return 0;
+}
+
+int
+validate_config(void)
+{
+	int rc = -1;
+
+	if (check_lcore_params() < 0) {
+		printf("check_lcore_params() failed\n");
+		goto exit;
+	}
+
+	if (init_lcore_rx_queues() < 0) {
+		printf("init_lcore_rx_queues() failed\n");
+		goto exit;
+	}
+
+	if (check_port_config() < 0) {
+		printf("check_port_config() failed\n");
+		goto exit;
+	}
+
+	return 0;
+
+exit:
+	return rc;
+}
+
+#define MEMPOOL_CACHE_SIZE 256
+
+/*
+ * This expression is used to calculate the number of mbufs needed
+ * depending on user input, taking  into account memory for rx and
+ * tx hardware rings, cache per lcore and mtable per port per lcore.
+ * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum
+ * value of 8192
+ */
+#define NB_MBUF(nports)                                                                            \
+	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores * RTE_GRAPH_BURST_SIZE +       \
+		 nports * nb_tx_queue * nb_txd + nb_lcores * MEMPOOL_CACHE_SIZE),                  \
+		8192u)
+
+static int
+init_mem(uint16_t portid, uint32_t nb_mbuf)
+{
+	uint32_t lcore_id;
+	int socketid;
+	char s[64];
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		if (numa_on)
+			socketid = rte_lcore_to_socket_id(lcore_id);
+		else
+			socketid = 0;
+
+		if (socketid >= NB_SOCKETS) {
+			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out of range %d\n",
+				 socketid, lcore_id, NB_SOCKETS);
+		}
+
+		if (pktmbuf_pool[portid][socketid] == NULL) {
+			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid, socketid);
+			/* Create a pool with priv size of a cacheline */
+			pktmbuf_pool[portid][socketid] = rte_pktmbuf_pool_create(
+				s, nb_mbuf, MEMPOOL_CACHE_SIZE, RTE_CACHE_LINE_SIZE,
+				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
+			if (pktmbuf_pool[portid][socketid] == NULL)
+				rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n",
+					 socketid);
+			else
+				printf("Allocated mbuf pool on socket %d\n", socketid);
+		}
+	}
+
+	return 0;
+}
+
+void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int ret;
+	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
+
+	printf("\nChecking link status");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid) {
+			if (force_quit)
+				return;
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+			memset(&link, 0, sizeof(link));
+			ret = rte_eth_link_get_nowait(portid, &link);
+			if (ret < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n", portid,
+					       rte_strerror(-ret));
+				continue;
+			}
+			/* Print link status if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_status_text, sizeof(link_status_text),
+						    &link);
+				printf("Port %d %s\n", portid, link_status_text);
+				continue;
+			}
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+		/* After finally printing all link status, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+	prompt_exit();
+}
+
+int
+graph_main_loop(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit & !graph_walk_quit)) {
+		if (likely(run_graph_walk))
+			rte_graph_walk(graph);
+	}
+
+	return 0;
+}
+
+struct rte_graph_cluster_stats *
+create_graph_cluster_stats(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stat;
+	const char *pattern = "worker_*";
+
+	/* 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;
+
+	stat = rte_graph_cluster_stats_create(&s_param);
+	if (stat == NULL)
+		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
+
+	return stat;
+}
+
+static void
+print_stats(void)
+{
+	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+	const char clr[] = {27, '[', '2', 'J', '\0'};
+
+	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);
+}
+
+int
+parse_config(const char *q_arg)
+{
+	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE, _NUM_FLD };
+	unsigned long int_fld[_NUM_FLD];
+	const char *p, *p0 = q_arg;
+	char *str_fld[_NUM_FLD];
+	uint32_t size;
+	char s[256];
+	char *end;
+	int i;
+
+	nb_lcore_params = 0;
+
+	while ((p = strchr(p0, '(')) != NULL) {
+		++p;
+		p0 = strchr(p, ')');
+		if (p0 == NULL)
+			goto exit;
+
+		size = p0 - p;
+		if (size >= sizeof(s))
+			goto exit;
+
+		memcpy(s, p, size);
+		s[size] = '\0';
+		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD)
+			goto exit;
+		for (i = 0; i < _NUM_FLD; i++) {
+			errno = 0;
+			int_fld[i] = strtoul(str_fld[i], &end, 0);
+			if (errno != 0 || end == str_fld[i])
+				goto exit;
+		}
+
+		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
+			printf("Exceeded max number of lcore params: %hu\n", nb_lcore_params);
+			goto exit;
+		}
+
+		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS || int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
+			printf("Invalid port/lcore id\n");
+			goto exit;
+		}
+
+		lcore_params_array[nb_lcore_params].port_id = (uint8_t)int_fld[FLD_PORT];
+		lcore_params_array[nb_lcore_params].queue_id = (uint8_t)int_fld[FLD_QUEUE];
+		lcore_params_array[nb_lcore_params].lcore_id = (uint8_t)int_fld[FLD_LCORE];
+		++nb_lcore_params;
+	}
+	lcore_params = lcore_params_array;
+
+	return 0;
+exit:
+	/* Revert to default config */
+	lcore_params = lcore_params_array_default;
+	nb_lcore_params = RTE_DIM(lcore_params_array_default);
+
+	return -1;
+}
+
+int
+parse_node_patterns(const char *q_arg)
+{
+	const char *p, *p0 = q_arg;
+	int ret = -EINVAL;
+	uint32_t size;
+
+	num_patterns = 0;
+
+	p = strchr(p0, '(');
+	if (p != NULL) {
+		++p;
+		while ((p0 = strchr(p, ',')) != NULL) {
+			size = p0 - p;
+			if (size >= RTE_NODE_NAMESIZE)
+				goto exit;
+
+			if (num_patterns >= MAX_NODE_PATTERNS) {
+				printf("Too many nodes passed.\n");
+				goto exit;
+			}
+
+			memcpy(node_pattern[num_patterns++], p, size);
+			p = p0 + 1;
+		}
+
+		p0 = strchr(p, ')');
+		if (p0 != NULL) {
+			size = p0 - p;
+			if (size >= RTE_NODE_NAMESIZE)
+				goto exit;
+
+			if (num_patterns >= MAX_NODE_PATTERNS) {
+				printf("Too many nodes passed.\n");
+				goto exit;
+			}
+
+			memcpy(node_pattern[num_patterns++], p, size);
+		} else {
+			goto exit;
+		}
+	} else {
+		goto exit;
+	}
+
+	return 0;
+exit:
+	return ret;
+}
+
+static void
+set_default_node_pattern(void)
+{
+	uint16_t idx;
+
+	for (idx = 0; idx < test_node_list[0].size; idx++)
+		strcpy(node_pattern[num_patterns++], test_node_list[0].nodes[idx]);
+}
+int
+validate_node_names(uint64_t *valid_nodes)
+{
+	rte_node_t node_cnt = rte_node_max_count();
+	bool pattern_matched = false;
+	rte_node_t id = 0;
+	int ret = -EINVAL;
+	uint16_t idx, i, j;
+
+	for (idx = 0; idx < num_patterns; idx++) {
+		for (id = 0; id < node_cnt; id++) {
+			if (strncmp(node_pattern[idx], rte_node_id_to_name(id),
+				    RTE_GRAPH_NAMESIZE) == 0)
+				break;
+		}
+		if (node_cnt == id) {
+			printf("Invalid node name passed\n");
+			return ret;
+		}
+	}
+
+	printf("num_ptrn:: %u\n", num_patterns);
+	for (i = 0; i < RTE_DIM(test_node_list); i++) {
+		idx = 0;
+		if (test_node_list[i].size == num_patterns) {
+			for (j = 0; j < num_patterns; j++) {
+				if (strncmp(node_pattern[j], test_node_list[i].nodes[j],
+				    RTE_GRAPH_NAMESIZE) == 0)
+					idx++;
+			}
+			printf("idx::%u\n", idx);
+			if (idx == num_patterns)
+				pattern_matched = true;
+		}
+	}
+
+	if (!pattern_matched) {
+		printf("Unsupported node pattern passed\n\n");
+		printf("Test supported node patterns are:\n");
+		for (i = 0; i < RTE_DIM(test_node_list); i++) {
+			printf("(");
+			for (j = 0; j < (test_node_list[i].size - 1); j++)
+				printf("%s,", test_node_list[i].nodes[j]);
+			printf("%s", test_node_list[i].nodes[j]);
+			printf(")\n");
+		}
+
+		return ret;
+	}
+
+	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
+		for (j = 0; j < num_patterns; j++) {
+			if (strncmp(node_pattern[j], supported_nodes[i].nodes[0],
+				    RTE_GRAPH_NAMESIZE) == 0) {
+				*valid_nodes |= supported_nodes[i].test_id;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+void
+cleanup_node_pattern(void)
+{
+	while (num_patterns) {
+		memset(node_pattern[num_patterns - 1], 0, RTE_GRAPH_NAMESIZE);
+		num_patterns--;
+	}
+}
+
+static int
+ethdev_tx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
+{
+	struct ethdev_tx_node_main *tx_node_data;
+	struct rte_node_register *tx_node;
+	char name[RTE_NODE_NAMESIZE];
+	uint16_t port_id;
+	uint32_t id;
+	int i;
+
+	tx_node_data = ethdev_tx_node_data_get();
+	tx_node = ethdev_tx_node_get();
+
+	for (i = 0; i < nb_confs; i++) {
+		port_id = conf[i].port_id;
+
+		if (!rte_eth_dev_is_valid_port(port_id))
+			return -EINVAL;
+
+		/* Create a per port tx node from base node */
+		snprintf(name, sizeof(name), "%u", port_id);
+		id = rte_node_clone(tx_node->id, name);
+		tx_node_data->nodes[port_id] = id;
+
+		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node->name, name, id);
+	}
+
+	return 0;
+}
+
+static int
+punt_kernel_node_configure(void)
+{
+	struct rte_node_register *punt_node;
+	char name[RTE_NODE_NAMESIZE];
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+	uint32_t id;
+
+	punt_node = punt_kernel_node_get();
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Create a per lcore punt_kernel node from base node */
+		snprintf(name, sizeof(name), "%u", lcore_id);
+		id = rte_node_clone(punt_node->id, name);
+		strcpy(qconf->punt_kernel_node_name, rte_node_id_to_name(id));
+
+		printf("punt_kernel node %s-%s: is at %u\n", punt_node->name, name, id);
+	}
+
+	return 0;
+}
+
+static int
+ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
+{
+	char name[RTE_NODE_NAMESIZE];
+	uint16_t i, j, port_id;
+	uint32_t id;
+
+	for (i = 0; i < nb_confs; i++) {
+		port_id = conf[i].port_id;
+
+		if (!rte_eth_dev_is_valid_port(port_id))
+			return -EINVAL;
+
+		/* Create node for each rx port queue pair */
+		for (j = 0; j < conf[i].num_rx_queues; j++) {
+			struct ethdev_rx_node_main *rx_node_data;
+			struct rte_node_register *rx_node;
+			ethdev_rx_node_elem_t *elem;
+
+			rx_node_data = ethdev_rx_get_node_data_get();
+			rx_node = ethdev_rx_node_get();
+			snprintf(name, sizeof(name), "%u-%u", port_id, j);
+			/* Clone a new rx node with same edges as parent */
+			id = rte_node_clone(rx_node->id, name);
+			if (id == RTE_NODE_ID_INVALID)
+				return -EIO;
+
+			/* Add it to list of ethdev rx nodes for lookup */
+			elem = malloc(sizeof(ethdev_rx_node_elem_t));
+			if (elem == NULL)
+				return -ENOMEM;
+			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
+			elem->ctx.port_id = port_id;
+			elem->ctx.queue_id = j;
+			elem->nid = id;
+			elem->next = rx_node_data->head;
+			rx_node_data->head = elem;
+
+			printf("ethdev:: Rx node %s-%s: is at %u\n", rx_node->name, name, id);
+		}
+	}
+
+	return 0;
+}
+
+static int
+update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
+{
+	struct ethdev_rx_node_main *rx_node_data;
+	ethdev_rx_node_elem_t *elem;
+	char *next_nodes[16];
+	rte_edge_t count;
+	uint16_t i;
+
+	count = rte_node_edge_count(id);
+	rte_node_edge_get(id, next_nodes);
+
+	for (i = 0; i < count; i++) {
+		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
+			rx_node_data = ethdev_rx_get_node_data_get();
+			elem = rx_node_data->head;
+			while (elem->next != rx_node_data->head) {
+				if (elem->nid == id)
+					break;
+				elem = elem->next;
+			}
+
+			if (elem->nid == id)
+				elem->ctx.cls_next = i;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int
+link_ethdev_rx_to_tx_node(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
+{
+	const char * const pattern[] = {"ethdev_tx-*"};
+	char name[RTE_NODE_NAMESIZE];
+	const char *next_node = name;
+	uint16_t i, j, port_id;
+	uint32_t rx_id;
+
+	for (i = 0; i < nb_confs; i++) {
+		port_id = conf[i].port_id;
+
+		if (!rte_eth_dev_is_valid_port(port_id))
+			return -EINVAL;
+
+		for (j = 0; j < conf[i].num_rx_queues; j++) {
+
+			snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id, j);
+			rx_id = rte_node_from_name(name);
+
+			/* Fill node pattern */
+			strcpy(node_pattern[num_patterns++], name);
+
+			/* Prepare the actual name of the cloned node */
+			snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
+
+			/* Update ethdev_rx node edges */
+			rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+			/* Fill node pattern */
+			strcpy(node_pattern[num_patterns++], name);
+
+			/* Update node_next details */
+			update_ethdev_rx_node_next(rx_id, pattern[0]);
+		}
+	}
+
+	return 0;
+}
+
+static int
+link_ethdev_rx_to_punt_kernel_node(void)
+{
+	uint16_t queueid, portid, queue;
+	char name[RTE_NODE_NAMESIZE];
+	const char *next_node = name;
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+	rte_node_t rx_id;
+	const char * const pattern[] = {"punt_kernel-*"};
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+		qconf = &lcore_conf[lcore_id];
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid, queueid);
+			rx_id = rte_node_from_name(name);
+
+			/* Fill node pattern */
+			strcpy(node_pattern[num_patterns++], name);
+
+			next_node = qconf->punt_kernel_node_name;
+
+			/* Fill node pattern */
+			strcpy(node_pattern[num_patterns++], next_node);
+
+			/* Update ethdev_rx node edges */
+			rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+			/* Update node_next details */
+			update_ethdev_rx_node_next(rx_id, pattern[0]);
+		}
+	}
+
+	return 0;
+}
+
+uint32_t
+ethdev_ports_setup(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint32_t nb_tx_queue, nb_lcores;
+	uint32_t nb_ports, nb_conf = 0;
+	uint8_t nb_rx_queue;
+	uint16_t portid;
+	int ret;
+
+	nb_ports = rte_eth_dev_count_avail();
+	nb_lcores = rte_lcore_count();
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		struct rte_eth_conf local_port_conf = port_conf;
+
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		/* Init port */
+		printf("Initializing port %d ... ", portid);
+		fflush(stdout);
+
+		nb_rx_queue = get_port_n_rx_queues(portid);
+		nb_tx_queue = nb_lcores;
+		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
+			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
+		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n", nb_rx_queue, nb_tx_queue);
+
+		rte_eth_dev_info_get(portid, &dev_info);
+
+		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 !=
+		    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, 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_rx_queue, nb_tx_queue, &local_port_conf);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%d\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=%d\n",
+				 ret, portid);
+
+		/* Init memory */
+		if (!per_port_pool)
+			ret = init_mem(0, NB_MBUF(nb_ports));
+		else
+			ret = init_mem(portid, NB_MBUF(1));
+
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
+		if (!per_port_pool)
+			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
+
+		else
+			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
+		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
+
+		nb_conf++;
+	}
+
+	return nb_conf;
+}
+
+void
+ethdev_rxq_configure(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t queueid, portid;
+	struct lcore_conf *qconf;
+	uint8_t queue, socketid;
+	uint32_t lcore_id;
+	int ret;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			struct rte_eth_rxconf rxq_conf;
+
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			if (numa_on)
+				socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
+			else
+				socketid = 0;
+
+			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
+			fflush(stdout);
+
+			rte_eth_dev_info_get(portid, &dev_info);
+			rxq_conf = dev_info.default_rxconf;
+			rxq_conf.offloads = port_conf.rxmode.offloads;
+			if (!per_port_pool)
+				ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
+							     &rxq_conf, pktmbuf_pool[0][socketid]);
+			else
+				ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
+							     &rxq_conf,
+							     pktmbuf_pool[portid][socketid]);
+			if (ret < 0)
+				rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup: err=%d, port=%d\n",
+					 ret, portid);
+
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+	}
+	printf("\n");
+}
+
+void
+ethdev_txq_configure(void)
+{
+	struct rte_eth_dev_info dev_info;
+	struct rte_eth_txconf *txconf;
+	uint16_t queueid, portid;
+	uint32_t lcore_id;
+	uint8_t socketid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		struct rte_eth_conf local_port_conf = port_conf;
+
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		rte_eth_dev_info_get(portid, &dev_info);
+
+		/* Init one TX queue per (lcore,port) pair */
+		queueid = 0;
+		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+			if (rte_lcore_is_enabled(lcore_id) == 0)
+				continue;
+
+			if (numa_on)
+				socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
+			else
+				socketid = 0;
+
+			printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
+			fflush(stdout);
+
+			txconf = &dev_info.default_txconf;
+			txconf->offloads = local_port_conf.txmode.offloads;
+			ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd, socketid, txconf);
+			if (ret < 0)
+				rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: err=%d, port=%d\n",
+					 ret, portid);
+			queueid++;
+		}
+	}
+	printf("\n");
+}
+
+int
+configure_graph_nodes(uint64_t valid_nodes)
+{
+	int ret = 0;
+
+	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
+		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
+		if (ret) {
+			printf("ethdev_rx_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
+		ret = ethdev_tx_node_configure(ethdev_conf, nb_conf);
+		if (ret) {
+			printf("ethdev_tx_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
+		ret = punt_kernel_node_configure();
+		if (ret) {
+			printf("punt_kernel_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	cleanup_node_pattern();
+
+	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_ETHDEV_RX_NODE)) {
+		ret = link_ethdev_rx_to_tx_node(ethdev_conf, nb_conf);
+		if (ret) {
+			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
+			goto exit;
+		}
+	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE | TEST_GRAPH_PUNT_KERNEL_NODE)) {
+		link_ethdev_rx_to_punt_kernel_node();
+	} else {
+		printf("Invalid node map\n");
+		ret = -EINVAL;
+	}
+
+exit:
+	return ret;
+}
+
+void
+start_eth_ports(void)
+{
+	uint16_t portid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		ret = rte_eth_dev_start(portid);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", ret, portid);
+
+		if (promiscuous_on)
+			rte_eth_promiscuous_enable(portid);
+	}
+}
+
+void
+stop_eth_ports(void)
+{
+	uint16_t portid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		ret = rte_eth_dev_stop(portid);
+		if (ret != 0)
+			printf("Failed to stop port %u: %s\n", portid, rte_strerror(-ret));
+		rte_eth_dev_close(portid);
+	}
+}
+
+int
+create_graph(char pattern[][RTE_NODE_NAMESIZE], uint8_t num_patterns)
+{
+	struct rte_graph_param graph_conf;
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+
+	node_patterns = malloc(MAX_NODE_PATTERNS * sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		for (i = 0; i < num_patterns; i++)
+			graph_conf.node_patterns[i] = pattern[i];
+
+		graph_conf.nb_node_patterns = num_patterns;
+
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u", lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE, "rte_graph_create(): graph_id invalid for lcore%u\n",
+				 lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph %s not found\n",
+				 qconf->name);
+	}
+
+	return 0;
+}
+
+int
+destroy_graph(void)
+{
+	uint32_t lcore_id;
+	rte_graph_t id;
+	int ret;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+	{
+		if (lcore_conf[lcore_id].graph) {
+			id = rte_graph_from_name(lcore_conf[lcore_id].name);
+			if (rte_graph_destroy(id)) {
+				printf("graph_id %u destroy failed.\n", id);
+				ret = -1;
+			}
+		}
+	}
+
+	if (node_patterns)
+		free(node_patterns);
+
+	return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+	uint64_t valid_nodes;
+	uint32_t lcore_id;
+	int ret;
+
+	graph_walk_quit = false;
+	force_quit = false;
+	interactive = 0;
+
+	node_patterns = NULL;
+
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	testgraph_logtype = rte_log_register("testgraph");
+	if (testgraph_logtype < 0)
+		rte_exit(EXIT_FAILURE, "Cannot register log type");
+
+	set_default_node_pattern();
+
+	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
+
+	ret = rte_eal_init(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n", rte_strerror(rte_errno));
+	argc -= ret;
+	argv += ret;
+
+	if (argc > 1) {
+		ret = parse_cmdline_args(argc, argv);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Invalid command line parameters\n");
+	}
+
+#ifdef RTE_LIB_CMDLINE
+	if (init_cmdline() != 0)
+		rte_exit(EXIT_FAILURE, "Could not initialise cmdline context.\n");
+
+	if (interactive == 1) {
+		prompt();
+	} else
+#endif
+	{
+		if (validate_config() < 0)
+			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
+
+		ret = validate_node_names(&valid_nodes);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "validate_node_names: err=%d\n", ret);
+
+		nb_conf = ethdev_ports_setup();
+
+		ethdev_rxq_configure();
+
+		ethdev_txq_configure();
+
+		ret = configure_graph_nodes(valid_nodes);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n", ret);
+
+		start_eth_ports();
+
+		check_all_ports_link_status(enabled_port_mask);
+
+		ret = create_graph(node_pattern, num_patterns);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
+
+		stats = create_graph_cluster_stats();
+		if (stats == NULL)
+			rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
+
+		/* Launch per-lcore init on every worker lcore */
+		rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
+
+		/* Accumulate and print stats on main until exit */
+		if (rte_graph_has_stats_feature())
+			print_stats();
+
+		/* Wait for worker cores to exit */
+		RTE_LCORE_FOREACH_WORKER(lcore_id) {
+			ret = rte_eal_wait_lcore(lcore_id);
+			if (ret < 0)
+				break;
+		}
+
+		ret = destroy_graph();
+
+		stop_eth_ports();
+	}
+
+	/* clean up the EAL */
+	ret = rte_eal_cleanup();
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-ret));
+
+	return EXIT_SUCCESS;
+}
diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
new file mode 100644
index 0000000000..b982d94781
--- /dev/null
+++ b/app/test-graph/testgraph.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef _TESTGRAPH_H_
+#define _TESTGRAPH_H_
+
+#include <stdbool.h>
+
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#include <cmdline.h>
+#include <cmdline_parse.h>
+
+
+#define MAX_LCORE_PARAMS 1024
+#define MAX_NODE_PATTERNS 128
+
+#ifndef BIT_ULL
+#define BIT_ULL(nr) (1ULL << (nr))
+#endif
+
+#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
+#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
+#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
+#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
+#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
+#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
+#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
+#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
+#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
+
+extern uint8_t cl_quit;
+static volatile bool force_quit;
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern uint32_t enabled_port_mask;
+extern uint32_t nb_conf;
+
+extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by default. */
+extern uint8_t interactive;	   /**< interactive mode is disabled by default. */
+extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
+extern int numa_on;		   /**< NUMA is enabled by default. */
+extern int per_port_pool;
+
+extern volatile bool graph_walk_quit;
+extern volatile bool run_graph_walk;
+extern struct rte_graph_cluster_stats *stats;
+
+extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
+extern uint8_t num_patterns;
+
+struct node_list {
+	const char *nodes[MAX_NODE_PATTERNS];
+	uint64_t test_id;
+	uint8_t size;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+void prompt(void);
+void prompt_exit(void);
+int init_cmdline(void);
+int validate_config(void);
+int parse_cmdline_args(int argc, char **argv);
+uint32_t ethdev_ports_setup(void);
+void ethdev_rxq_configure(void);
+void ethdev_txq_configure(void);
+void start_eth_ports(void);
+void stop_eth_ports(void);
+int create_graph(char pattern[][RTE_NODE_NAMESIZE], uint8_t num_patterns);
+int destroy_graph(void);
+int graph_main_loop(void *conf);
+struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
+void check_all_ports_link_status(uint32_t port_mask);
+int configure_graph_nodes(uint64_t valid_nodes);
+
+int parse_config(const char *q_arg);
+int parse_node_patterns(const char *q_arg);
+int validate_node_names(uint64_t *valid_nodes);
+void cleanup_node_pattern(void);
+
+#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
+	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt, ##args)
+
+#endif /* _TESTGRAPH_H_ */
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index 6f84fc31ff..f18c508fa2 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -20,6 +20,7 @@ DPDK Tools User Guides
     cryptoperf
     comp_perf
     testeventdev
+    testgraph
     testregex
     testmldev
     dts
diff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst
new file mode 100644
index 0000000000..3c1e058724
--- /dev/null
+++ b/doc/guides/tools/testgraph.rst
@@ -0,0 +1,131 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(C) 2023 Marvell International Ltd.
+
+dpdk-test-graph Application
+===========================
+
+The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK) application that allows
+exercising various graph library features. This application has a generic framework to add
+new test configurations and expand test coverage to verify the functionality of graph nodes
+and observe the graph cluster statistics.
+
+Running the Application
+-----------------------
+
+The application has a number of command line options:
+
+.. code-block:: console
+
+   dpdk-test-eventdev [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+The following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-test-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bitmask of the cores to run on. The corelist is a
+        list of cores to use.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+The following are the application command-line options:
+
+* ``-p <n>``
+
+        Set the ethdev port mask.
+
+* ``-P``
+
+        Set the ethdev ports in promiscuous mode.
+
+* ``--config <config>``
+
+        Set the Rxq configuration.
+        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
+
+* ``--node-pattern <n>``
+
+        Set the node patterns to use in graph creation.
+        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
+
+* ``--per-port-pool``
+
+        Use separate buffer pool per port.
+
+* ``--no-numa``
+
+        Disable numa awareness.
+
+* ``--interactive``
+
+        Switch to interactive mode.
+
+Running the Tool
+~~~~~~~~~~~~~~~~
+
+Here is the sample command line to run simple iofwd test::
+
+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
+       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
+
+Below is a sample command line to punt rx packets to kernel::
+
+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
+       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
+
+Interactive mode
+~~~~~~~~~~~~~~~~
+
+Tool uses ``--interactive`` command line option to enter interactive mode and use cmdline options
+to setup the required node configurations, create graph and than start graph_walk.
+
+
+testgraph> help
+
+Help is available for the following sections:
+
+    help control                    : Start and stop graph walk.
+    help display                    : Displaying port, stats and config information.
+    help config                     : Configuration information.
+    help all                        : All of the above sections.
+
+testgraph> help all
+
+Control forwarding:
+
+start graph_walk
+ Start graph_walk on worker threads.
+
+stop graph_walk
+ Stop worker threads from running graph_walk.
+
+quit
+ Quit to prompt.
+
+
+Display:
+
+show node_list
+ Display the list of supported nodes.
+
+show graph_stats
+ Display the node statistics of graph cluster.
+
+
+Configuration:
+
+set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
+ Set lcore configuration.
+
+create_graph (node0_name,node1_name,...,nodeX_name)
+ Create graph instances using the provided node details.
+
+destroy_graph
+ Destroy the graph instances.
+
+testgraph>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v2 0/4] app: introduce testgraph application
  2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
                   ` (3 preceding siblings ...)
  2023-04-21  6:02 ` [PATCH 4/4] app: add testgraph application Vamsi Attunuru
@ 2023-04-25 13:15 ` Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
                     ` (4 more replies)
  4 siblings, 5 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-25 13:15 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram

This patch series introduces testgraph application that verifies graph
architecture, it provides an infra to verify the graph & node libraries
and scale the test coverage by adding newer configurations to exercise
various graph topologies & graph-walk models required by the DPDK
applications.

Also this series adds two new nodes (punt_kernel & kernel_recv) to the
node library.

V2:
* Handle error checks in testgraph application
* Extend supported test node patterns
* Fix warnings

Vamsi Attunuru (4):
  node: add pkt punt to kernel node
  node: add a node to receive pkts from kernel
  node: remove hardcoded node next details
  app: add testgraph application

 app/meson.build                     |    1 +
 app/test-graph/cmdline.c            |  211 ++++
 app/test-graph/cmdline_graph.c      |  294 ++++++
 app/test-graph/cmdline_graph.h      |   19 +
 app/test-graph/meson.build          |   14 +
 app/test-graph/parameters.c         |  157 +++
 app/test-graph/testgraph.c          | 1426 +++++++++++++++++++++++++++
 app/test-graph/testgraph.h          |   91 ++
 doc/guides/prog_guide/graph_lib.rst |   17 +
 doc/guides/tools/index.rst          |    1 +
 doc/guides/tools/testgraph.rst      |  131 +++
 lib/node/ethdev_rx.c                |    2 -
 lib/node/kernel_recv.c              |  276 ++++++
 lib/node/kernel_recv_priv.h         |   74 ++
 lib/node/meson.build                |    2 +
 lib/node/punt_kernel.c              |  125 +++
 lib/node/punt_kernel_priv.h         |   36 +
 17 files changed, 2875 insertions(+), 2 deletions(-)
 create mode 100644 app/test-graph/cmdline.c
 create mode 100644 app/test-graph/cmdline_graph.c
 create mode 100644 app/test-graph/cmdline_graph.h
 create mode 100644 app/test-graph/meson.build
 create mode 100644 app/test-graph/parameters.c
 create mode 100644 app/test-graph/testgraph.c
 create mode 100644 app/test-graph/testgraph.h
 create mode 100644 doc/guides/tools/testgraph.rst
 create mode 100644 lib/node/kernel_recv.c
 create mode 100644 lib/node/kernel_recv_priv.h
 create mode 100644 lib/node/punt_kernel.c
 create mode 100644 lib/node/punt_kernel_priv.h

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v2 1/4] node: add pkt punt to kernel node
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
@ 2023-04-25 13:15   ` Vamsi Attunuru
  2023-05-30  8:16     ` Nithin Dabilpuram
  2023-04-25 13:15   ` [PATCH v2 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-25 13:15 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds a node to punt the packets to kernel over
a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |  10 +++
 lib/node/meson.build                |   1 +
 lib/node/punt_kernel.c              | 125 ++++++++++++++++++++++++++++
 lib/node/punt_kernel_priv.h         |  36 ++++++++
 4 files changed, 172 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index 1cfdc86433..b3b5b14827 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -392,3 +392,13 @@ null
 ~~~~
 This node ignores the set of objects passed to it and reports that all are
 processed.
+
+punt_kernel
+~~~~~~~~~~~
+This node punts the packets to kernel using a raw socket interface. For sending
+the received packets, raw socket uses the packet's destination IP address in
+sockaddr_in address structure and node uses ``sendto`` function to send data
+on the raw socket.
+
+Aftering sending the burst of packets to kernel, this node redirects the same
+objects to pkt_drop node to free up the packet buffers.
diff --git a/lib/node/meson.build b/lib/node/meson.build
index dbdf673c86..48c2da73f7 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'null.c',
         'pkt_cls.c',
         'pkt_drop.c',
+        'punt_kernel.c',
 )
 headers = files('rte_node_ip4_api.h', 'rte_node_eth_api.h')
 # Strict-aliasing rules are violated by uint8_t[] to context size casts.
diff --git a/lib/node/punt_kernel.c b/lib/node/punt_kernel.c
new file mode 100644
index 0000000000..e5dd15b759
--- /dev/null
+++ b/lib/node/punt_kernel.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+
+#include "node_private.h"
+#include "punt_kernel_priv.h"
+
+static __rte_always_inline void
+punt_kernel_process_mbuf(struct rte_node *node, struct rte_mbuf **mbufs, uint16_t cnt)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+	struct sockaddr_in sin = {0};
+	struct rte_ipv4_hdr *ip4;
+	size_t len;
+	char *buf;
+	int i;
+
+	for (i = 0; i < cnt; i++) {
+		ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
+		len = rte_pktmbuf_data_len(mbufs[i]);
+		buf = (char *)ip4;
+
+		sin.sin_family = AF_INET;
+		sin.sin_port = 0;
+		sin.sin_addr.s_addr = ip4->dst_addr;
+
+		if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+			node_err("punt_kernel", "Unable to send packets: %s\n", strerror(errno));
+	}
+}
+
+static uint16_t
+punt_kernel_node_process(struct rte_graph *graph __rte_unused, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
+	uint16_t obj_left = nb_objs;
+
+#define PREFETCH_CNT 4
+
+	while (obj_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *, pkts[7]->l2_len));
+
+		punt_kernel_process_mbuf(node, pkts, PREFETCH_CNT);
+
+		obj_left -= PREFETCH_CNT;
+		pkts += PREFETCH_CNT;
+	}
+
+	while (obj_left > 0) {
+		punt_kernel_process_mbuf(node, pkts, 1);
+
+		obj_left--;
+		pkts++;
+	}
+
+	rte_node_next_stream_move(graph, node, PUNT_KERNEL_NEXT_PKT_DROP);
+
+	return nb_objs;
+}
+
+static int
+punt_kernel_node_init(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+
+	ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (ctx->sock < 0)
+		node_err("punt_kernel", "Unable to open RAW socket\n");
+
+	return 0;
+}
+
+static void
+punt_kernel_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
+
+	if (ctx->sock >= 0) {
+		close(ctx->sock);
+		ctx->sock = -1;
+	}
+}
+
+static struct rte_node_register punt_kernel_node_base = {
+	.process = punt_kernel_node_process,
+	.name = "punt_kernel",
+
+	.init = punt_kernel_node_init,
+	.fini = punt_kernel_node_fini,
+
+	.nb_edges = PUNT_KERNEL_NEXT_MAX,
+	.next_nodes = {
+			[PUNT_KERNEL_NEXT_PKT_DROP] = "pkt_drop",
+	},
+};
+
+struct rte_node_register *
+punt_kernel_node_get(void)
+{
+	return &punt_kernel_node_base;
+}
+
+RTE_NODE_REGISTER(punt_kernel_node_base);
diff --git a/lib/node/punt_kernel_priv.h b/lib/node/punt_kernel_priv.h
new file mode 100644
index 0000000000..25ba2072e7
--- /dev/null
+++ b/lib/node/punt_kernel_priv.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __INCLUDE_PUNT_KERNEL_PRIV_H__
+#define __INCLUDE_PUNT_KERNEL_PRIV_H__
+
+struct punt_kernel_node_elem;
+struct punt_kernel_node_ctx;
+typedef struct punt_kernel_node_elem punt_kernel_node_elem_t;
+
+/**
+ * @internal
+ *
+ * PUNT Kernel node context structure.
+ */
+typedef struct punt_kernel_node_ctx {
+	int sock;
+} punt_kernel_node_ctx_t;
+
+enum punt_kernel_next_nodes {
+	PUNT_KERNEL_NEXT_PKT_DROP,
+	PUNT_KERNEL_NEXT_MAX,
+};
+
+/**
+ * @internal
+ *
+ * Get the PUNT Kernel node.
+ *
+ * @return
+ *   Pointer to the PUNT Kernel node.
+ */
+struct rte_node_register *punt_kernel_node_get(void);
+
+#endif /* __INCLUDE_PUNT_KERNEL_PRIV_H__ */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v2 2/4] node: add a node to receive pkts from kernel
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
@ 2023-04-25 13:15   ` Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 3/4] node: remove hardcoded node next details Vamsi Attunuru
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-25 13:15 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds a node to receive packets from kernel
over a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |   7 +
 lib/node/kernel_recv.c              | 276 ++++++++++++++++++++++++++++
 lib/node/kernel_recv_priv.h         |  74 ++++++++
 lib/node/meson.build                |   1 +
 4 files changed, 358 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index b3b5b14827..1057f16de8 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -402,3 +402,10 @@ on the raw socket.
 
 Aftering sending the burst of packets to kernel, this node redirects the same
 objects to pkt_drop node to free up the packet buffers.
+
+kernel_recv
+~~~~~~~~~~~
+This node receives packets from kernel over a raw socket interface. Uses ``poll``
+function to poll on the socket fd for ``POLLIN`` events to read the packets from
+raw socket to stream buffer and does ``rte_node_next_stream_move()`` when there
+are received packets.
diff --git a/lib/node/kernel_recv.c b/lib/node/kernel_recv.c
new file mode 100644
index 0000000000..9b28ad76d3
--- /dev/null
+++ b/lib/node/kernel_recv.c
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+#include <rte_malloc.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_net.h>
+
+#include "ethdev_rx_priv.h"
+#include "kernel_recv_priv.h"
+#include "node_private.h"
+
+static struct kernel_recv_node_main kernel_recv_main;
+
+static inline struct rte_mbuf *
+alloc_rx_mbuf(kernel_recv_node_ctx_t *ctx)
+{
+	kernel_recv_info_t *rx = ctx->recv_info;
+
+	if (rx->idx >= rx->cnt) {
+		uint16_t cnt;
+
+		rx->idx = 0;
+		rx->cnt = 0;
+
+		cnt = rte_pktmbuf_alloc_bulk(ctx->pktmbuf_pool, rx->rx_bufs, KERN_RECV_CACHE_COUNT);
+		if (cnt <= 0)
+			return NULL;
+
+		rx->cnt = cnt;
+	}
+
+	return rx->rx_bufs[rx->idx++];
+}
+
+static inline void
+mbuf_update(struct rte_mbuf **mbufs, uint16_t nb_pkts)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	int i;
+
+	for (i = 0; i < nb_pkts; i++) {
+		m = mbufs[i];
+
+		m->packet_type = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+		m->ol_flags = 0;
+		m->tx_offload = 0;
+
+		m->l2_len = hdr_lens.l2_len;
+		m->l3_len = hdr_lens.l3_len;
+		m->l4_len = hdr_lens.l4_len;
+	}
+}
+
+static uint16_t
+recv_pkt_parse(void **objs, uint16_t nb_pkts)
+{
+	uint16_t pkts_left = nb_pkts;
+	struct rte_mbuf **pkts;
+	int i;
+
+	pkts = (struct rte_mbuf **)objs;
+
+	if (pkts_left >= 4) {
+		for (i = 0; i < 4; i++)
+			rte_prefetch0(rte_pktmbuf_mtod(pkts[i], void *));
+	}
+
+	while (pkts_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[4], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[5], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[6], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[7], void *));
+
+		/* Extract ptype of mbufs */
+		mbuf_update(pkts, 4);
+
+		pkts += 4;
+		pkts_left -= 4;
+	}
+
+	if (pkts_left > 0)
+		mbuf_update(pkts, pkts_left);
+
+	return nb_pkts;
+}
+
+static uint16_t
+kernel_recv_node_do(struct rte_graph *graph, struct rte_node *node, kernel_recv_node_ctx_t *ctx)
+{
+	kernel_recv_info_t *rx;
+	uint16_t next_index;
+	int fd;
+
+	rx = ctx->recv_info;
+	next_index = rx->cls_next;
+
+	fd = rx->sock;
+	if (fd > 0) {
+		struct rte_mbuf **mbufs;
+		uint16_t len = 0, count = 0;
+		int nb_cnt, i;
+
+		nb_cnt = (node->size >= RTE_GRAPH_BURST_SIZE) ? RTE_GRAPH_BURST_SIZE : node->size;
+
+		mbufs = (struct rte_mbuf **)node->objs;
+		for (i = 0; i < nb_cnt; i++) {
+			struct rte_mbuf *m = alloc_rx_mbuf(ctx);
+
+			if (!m)
+				break;
+
+			len = read(fd, rte_pktmbuf_mtod(m, char *), rte_pktmbuf_tailroom(m));
+			if (len == 0 || len == 0xFFFF) {
+				rte_pktmbuf_free(m);
+				if (rx->idx <= 0)
+					node_dbg("kernel_recv", "rx_mbuf array is empty\n");
+				rx->idx--;
+				break;
+			}
+			*mbufs++ = m;
+
+			m->port = node->id;
+			rte_pktmbuf_data_len(m) = len;
+
+			count++;
+		}
+
+		if (count) {
+			recv_pkt_parse(node->objs, count);
+			node->idx = count;
+
+			/* Enqueue to next node */
+			rte_node_next_stream_move(graph, node, next_index);
+		}
+
+		return count;
+	}
+
+	return 0;
+}
+
+static uint16_t
+kernel_recv_node_process(struct rte_graph *graph, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+	int fd;
+
+	RTE_SET_USED(objs);
+	RTE_SET_USED(nb_objs);
+
+	if (!ctx)
+		return 0;
+
+	fd = ctx->recv_info->sock;
+	if (fd > 0) {
+		struct pollfd fds = {.fd = fd, .events = POLLIN};
+
+		if (poll(&fds, 1, 0) > 0) {
+			if (fds.revents & POLLIN)
+				return kernel_recv_node_do(graph, node, ctx);
+		}
+	}
+
+	return 0;
+}
+
+static int
+kernel_recv_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+	kernel_recv_node_elem_t *elem = kernel_recv_main.head;
+	kernel_recv_info_t *recv_info;
+	int sock;
+
+	while (elem) {
+		if (elem->nid == node->id) {
+			/* Update node specific context */
+			memcpy(ctx, &elem->ctx, sizeof(kernel_recv_node_ctx_t));
+			break;
+		}
+		elem = elem->next;
+	}
+
+	RTE_VERIFY(elem != NULL);
+
+	if (ctx->pktmbuf_pool == NULL) {
+		node_err("kernel_recv", "Invalid mbuf pool on graph %s\n", graph->name);
+		return -EINVAL;
+	}
+
+	recv_info =
+		rte_zmalloc("kernel_recv_info", sizeof(kernel_recv_info_t), RTE_CACHE_LINE_SIZE);
+	if (!recv_info) {
+		node_err("kernel_recv", "Kernel recv_info is NULL\n");
+		return -ENOMEM;
+	}
+
+	sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (sock < 0) {
+		node_err("kernel_recv", "Unable to open RAW socket\n");
+		return sock;
+	}
+
+	recv_info->sock = sock;
+	ctx->recv_info = recv_info;
+
+	return 0;
+}
+
+static void
+kernel_recv_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	kernel_recv_node_ctx_t *ctx = (kernel_recv_node_ctx_t *)node->ctx;
+
+	if (ctx->recv_info) {
+		close(ctx->recv_info->sock);
+		ctx->recv_info->sock = -1;
+		rte_free(ctx->recv_info);
+	}
+
+	ctx->recv_info = NULL;
+}
+
+struct kernel_recv_node_main *
+kernel_recv_node_data_get(void)
+{
+	return &kernel_recv_main;
+}
+
+static struct rte_node_register kernel_recv_node_base = {
+	.process = kernel_recv_node_process,
+	.flags = RTE_NODE_SOURCE_F,
+	.name = "kernel_recv",
+
+	.init = kernel_recv_node_init,
+	.fini = kernel_recv_node_fini,
+
+	.nb_edges = KERNEL_RECV_NEXT_MAX,
+	.next_nodes = {
+			/* Default pkt classification node */
+			[KERNEL_RECV_NEXT_PKT_CLS] = "pkt_cls",
+			[KERNEL_RECV_NEXT_IP4_LOOKUP] = "ip4_lookup",
+	},
+};
+
+struct rte_node_register *
+kernel_recv_node_get(void)
+{
+	return &kernel_recv_node_base;
+}
+
+RTE_NODE_REGISTER(kernel_recv_node_base);
diff --git a/lib/node/kernel_recv_priv.h b/lib/node/kernel_recv_priv.h
new file mode 100644
index 0000000000..c2bfbf40bd
--- /dev/null
+++ b/lib/node/kernel_recv_priv.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __INCLUDE_KERNEL_RECV_PRIV_H__
+#define __INCLUDE_KERNEL_RECV_PRIV_H__
+
+#define KERN_RECV_CACHE_COUNT 64
+
+typedef struct kernel_recv_info {
+	struct rte_mbuf *rx_bufs[KERN_RECV_CACHE_COUNT];
+	uint16_t cls_next;
+	uint16_t idx;
+	uint16_t cnt;
+	int sock;
+} kernel_recv_info_t;
+
+/**
+ * @internal
+ *
+ * Kernel Recv node context structure.
+ */
+typedef struct kernel_recv_node_ctx {
+	struct rte_mempool *pktmbuf_pool;
+	kernel_recv_info_t *recv_info;
+} kernel_recv_node_ctx_t;
+
+/**
+ * @internal
+ *
+ * Kernel Recv node list element structure.
+ */
+typedef struct kernel_recv_node_elem {
+	struct kernel_recv_node_elem *next; /**< Pointer to the next node element. */
+	struct kernel_recv_node_ctx ctx;    /**< Kernel Recv node context. */
+	rte_node_t nid;			    /**< Node identifier of the Kernel Recv node. */
+} kernel_recv_node_elem_t;
+
+enum kernel_recv_next_nodes {
+	KERNEL_RECV_NEXT_IP4_LOOKUP,
+	KERNEL_RECV_NEXT_PKT_CLS,
+	KERNEL_RECV_NEXT_MAX,
+};
+
+/**
+ * @internal
+ *
+ * Kernel Recv node main structure.
+ */
+struct kernel_recv_node_main {
+	kernel_recv_node_elem_t *head; /**< Pointer to the head node element. */
+};
+
+/**
+ * @internal
+ *
+ * Get the Kernel Recv node data.
+ *
+ * @return
+ *   Pointer to Kernel Recv node data.
+ */
+struct kernel_recv_node_main *kernel_recv_node_data_get(void);
+
+/**
+ * @internal
+ *
+ * Get the Kernel Recv node.
+ *
+ * @return
+ *   Pointer to the Kernel Recv node.
+ */
+struct rte_node_register *kernel_recv_node_get(void);
+
+#endif /* __INCLUDE_KERNEL_RECV_PRIV_H__ */
diff --git a/lib/node/meson.build b/lib/node/meson.build
index 48c2da73f7..8b1b024f61 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'ethdev_tx.c',
         'ip4_lookup.c',
         'ip4_rewrite.c',
+        'kernel_recv.c',
         'log.c',
         'null.c',
         'pkt_cls.c',
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v2 3/4] node: remove hardcoded node next details
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
@ 2023-04-25 13:15   ` Vamsi Attunuru
  2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
  4 siblings, 0 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-25 13:15 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram

For ethdev_rx node, node_next details can be populated
during node cloning time and same gets assigned to
node context structure during node initialization.

Patch removes overriding node_next details in node
init().

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 lib/node/ethdev_rx.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lib/node/ethdev_rx.c b/lib/node/ethdev_rx.c
index a19237b42f..85816c489c 100644
--- a/lib/node/ethdev_rx.c
+++ b/lib/node/ethdev_rx.c
@@ -194,8 +194,6 @@ ethdev_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
 
 	RTE_VERIFY(elem != NULL);
 
-	ctx->cls_next = ETHDEV_RX_NEXT_PKT_CLS;
-
 	/* Check and setup ptype */
 	return ethdev_ptype_setup(ctx->port_id, ctx->queue_id);
 }
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v2 4/4] app: add testgraph application
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
                     ` (2 preceding siblings ...)
  2023-04-25 13:15   ` [PATCH v2 3/4] node: remove hardcoded node next details Vamsi Attunuru
@ 2023-04-25 13:15   ` Vamsi Attunuru
  2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
                       ` (2 more replies)
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
  4 siblings, 3 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-04-25 13:15 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram

Patch adds test-graph application to validate graph
and node libraries.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 app/meson.build                |    1 +
 app/test-graph/cmdline.c       |  211 +++++
 app/test-graph/cmdline_graph.c |  294 +++++++
 app/test-graph/cmdline_graph.h |   19 +
 app/test-graph/meson.build     |   14 +
 app/test-graph/parameters.c    |  157 ++++
 app/test-graph/testgraph.c     | 1426 ++++++++++++++++++++++++++++++++
 app/test-graph/testgraph.h     |   91 ++
 doc/guides/tools/index.rst     |    1 +
 doc/guides/tools/testgraph.rst |  131 +++
 10 files changed, 2345 insertions(+)

diff --git a/app/meson.build b/app/meson.build
index 74d2420f67..6c7b24e604 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -22,6 +22,7 @@ apps = [
         'test-eventdev',
         'test-fib',
         'test-flow-perf',
+        'test-graph',
         'test-gpudev',
         'test-mldev',
         'test-pipeline',
diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
new file mode 100644
index 0000000000..d9474d827a
--- /dev/null
+++ b/app/test-graph/cmdline.c
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <stdlib.h>
+
+#include <cmdline.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_etheraddr.h>
+#include <cmdline_parse_ipaddr.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_rdline.h>
+#include <cmdline_socket.h>
+
+#include "cmdline_graph.h"
+#include "testgraph.h"
+
+static struct cmdline *testgraph_cl;
+static cmdline_parse_ctx_t *main_ctx;
+
+/* *** Help command with introduction. *** */
+struct cmd_help_brief_result {
+	cmdline_fixed_string_t help;
+};
+
+static void
+cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	cmdline_printf(cl,
+		       "\n"
+		       "Help is available for the following sections:\n\n"
+		       "    help control                    : Start and stop graph walk.\n"
+		       "    help display                    : Displaying port, stats and config "
+		       "information.\n"
+		       "    help config                     : Configuration information.\n"
+		       "    help all                        : All of the above sections.\n\n");
+}
+
+static cmdline_parse_token_string_t cmd_help_brief_help =
+	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help, "help");
+
+static cmdline_parse_inst_t cmd_help_brief = {
+	.f = cmd_help_brief_parsed,
+	.data = NULL,
+	.help_str = "help: Show help",
+	.tokens = {
+			(void *)&cmd_help_brief_help,
+			NULL,
+		},
+};
+
+/* *** Help command with help sections. *** */
+struct cmd_help_long_result {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t section;
+};
+
+static void
+cmd_help_long_parsed(void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	int show_all = 0;
+	struct cmd_help_long_result *res = parsed_result;
+
+	if (!strcmp(res->section, "all"))
+		show_all = 1;
+
+	if (show_all || !strcmp(res->section, "control")) {
+
+		cmdline_printf(cl, "\n"
+				   "Control forwarding:\n"
+				   "-------------------\n\n"
+
+				   "start graph_walk\n"
+				   " Start graph_walk on worker threads.\n\n"
+
+				   "stop graph_walk\n"
+				   " Stop worker threads from running graph_walk.\n\n"
+
+				   "quit\n"
+				   "    Quit to prompt.\n\n");
+	}
+
+	if (show_all || !strcmp(res->section, "display")) {
+
+		cmdline_printf(cl,
+			       "\n"
+			       "Display:\n"
+			       "--------\n\n"
+
+			       "show node_list\n"
+			       " Display the list of supported nodes.\n\n"
+
+			       "show graph_stats\n"
+			       " Display the node statistics of graph cluster.\n\n");
+	}
+
+	if (show_all || !strcmp(res->section, "config")) {
+		cmdline_printf(cl, "\n"
+				   "Configuration:\n"
+				   "--------------\n"
+				   "set lcore_config (port_id0,rxq0,lcore_idX),..."
+				   ".....,(port_idX,rxqX,lcoreidY)\n"
+				   " Set lcore configuration.\n\n"
+
+				   "create_graph (node0_name,node1_name,...,nodeX_name)\n"
+				   " Create graph instances using the provided node details.\n\n"
+
+				   "destroy_graph\n"
+				   " Destroy the graph instances.\n\n");
+	}
+}
+
+static cmdline_parse_token_string_t cmd_help_long_help =
+	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help, "help");
+
+static cmdline_parse_token_string_t cmd_help_long_section = TOKEN_STRING_INITIALIZER(
+	struct cmd_help_long_result, section, "all#control#display#config");
+
+static cmdline_parse_inst_t cmd_help_long = {
+	.f = cmd_help_long_parsed,
+	.data = NULL,
+	.help_str = "help all|control|display|config: "
+		    "Show help",
+	.tokens = {
+			(void *)&cmd_help_long_help,
+			(void *)&cmd_help_long_section,
+			NULL,
+		},
+};
+
+/* *** QUIT *** */
+struct cmd_quit_result {
+	cmdline_fixed_string_t quit;
+};
+
+static void
+cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
+{
+	cmdline_quit(cl);
+}
+
+static cmdline_parse_token_string_t cmd_quit_quit =
+	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
+
+static cmdline_parse_inst_t cmd_quit = {
+	.f = cmd_quit_parsed,
+	.data = NULL,
+	.help_str = "quit: Exit application",
+	.tokens = {
+			(void *)&cmd_quit_quit,
+			NULL,
+		},
+};
+
+/* list of instructions */
+static cmdline_parse_ctx_t builtin_ctx[] = {
+	(cmdline_parse_inst_t *)&cmd_help_brief,
+	(cmdline_parse_inst_t *)&cmd_help_long,
+	(cmdline_parse_inst_t *)&cmd_quit,
+	(cmdline_parse_inst_t *)&cmd_show_node_list,
+	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
+	(cmdline_parse_inst_t *)&cmd_create_graph,
+	(cmdline_parse_inst_t *)&cmd_destroy_graph,
+	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
+	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
+	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
+	NULL,
+};
+
+int
+init_cmdline(void)
+{
+	unsigned int count;
+	unsigned int i;
+
+	count = 0;
+	for (i = 0; builtin_ctx[i] != NULL; i++)
+		count++;
+
+	/* cmdline expects a NULL terminated array */
+	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
+	if (main_ctx == NULL)
+		return -1;
+
+	count = 0;
+	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
+		main_ctx[count] = builtin_ctx[i];
+
+	return 0;
+}
+
+void
+prompt_exit(void)
+{
+	cmdline_quit(testgraph_cl);
+}
+
+/* prompt function, called from main on MAIN lcore */
+void
+prompt(void)
+{
+	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
+	if (testgraph_cl == NULL) {
+		fprintf(stderr, "Failed to create stdin based cmdline context\n");
+		return;
+	}
+
+	cmdline_interact(testgraph_cl);
+	cmdline_stdin_exit(testgraph_cl);
+}
diff --git a/app/test-graph/cmdline_graph.c b/app/test-graph/cmdline_graph.c
new file mode 100644
index 0000000000..d66149a224
--- /dev/null
+++ b/app/test-graph/cmdline_graph.c
@@ -0,0 +1,294 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+
+#include "cmdline_graph.h"
+#include "testgraph.h"
+
+/* *** Show supported node details *** */
+struct cmd_show_node_list_result {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t node_list;
+};
+
+static cmdline_parse_token_string_t cmd_show_node_list_show =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, show, "show");
+static cmdline_parse_token_string_t cmd_show_node_list_node_list =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, node_list, "node_list");
+
+static void
+cmd_show_node_list_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			  __rte_unused void *data)
+{
+	rte_node_t node_cnt = rte_node_max_count();
+	rte_node_t id;
+
+	printf("\n**** Supported Graph Nodes ****\n");
+	for (id = 0; id < node_cnt; id++)
+		printf("%s\n", rte_node_id_to_name(id));
+
+	printf("********************************\n");
+}
+
+cmdline_parse_inst_t cmd_show_node_list = {
+	.f = cmd_show_node_list_parsed,
+	.data = NULL,
+	.help_str = "show node_list",
+	.tokens = {
+			(void *)&cmd_show_node_list_show,
+			(void *)&cmd_show_node_list_node_list,
+			NULL,
+		},
+};
+
+/* *** Set lcore config *** */
+struct cmd_set_lcore_config_result {
+	cmdline_fixed_string_t set;
+	cmdline_fixed_string_t lcore_config;
+	cmdline_multi_string_t token_string;
+};
+
+static cmdline_parse_token_string_t cmd_set_lcore_config_set =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set, "set");
+static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, lcore_config, "lcore_config");
+static cmdline_parse_token_string_t cmd_set_lcore_config_token_string = TOKEN_STRING_INITIALIZER(
+	struct cmd_set_lcore_config_result, token_string, TOKEN_STRING_MULTI);
+
+static void
+cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	struct cmd_set_lcore_config_result *res = parsed_result;
+	const char *t_str = res->token_string;
+	int ret;
+
+	/* Parse string */
+	ret = parse_config(t_str);
+	if (ret) {
+		printf(" lcore_config string parse error\n");
+		return;
+	}
+
+	validate_config();
+}
+
+cmdline_parse_inst_t cmd_set_lcore_config = {
+	.f = cmd_set_lcore_config_parsed,
+	.data = NULL,
+	.help_str = "set lcore_config "
+		    "(port,queue,lcore),[(port,queue,lcore) ... (port,queue,lcore)]",
+	.tokens = {
+			(void *)&cmd_set_lcore_config_set,
+			(void *)&cmd_set_lcore_config_lcore_config,
+			(void *)&cmd_set_lcore_config_token_string,
+			NULL,
+		},
+};
+
+/* *** Create graph *** */
+struct cmd_create_graph_result {
+	cmdline_fixed_string_t create_graph;
+	cmdline_multi_string_t token_string;
+};
+
+static cmdline_parse_token_string_t cmd_create_graph_create_graph =
+	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, create_graph, "create_graph");
+static cmdline_parse_token_string_t cmd_create_graph_token_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, token_string, TOKEN_STRING_MULTI);
+
+static void
+cmd_create_graph_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
+			__rte_unused void *data)
+{
+	struct cmd_create_graph_result *res = parsed_result;
+	const char *t_str = res->token_string;
+	uint64_t valid_nodes = 0;
+	int ret;
+
+	ret = parse_node_patterns(t_str);
+	if (ret) {
+		printf("parse_node_patterns failed\n");
+		cleanup_node_pattern();
+		return;
+	}
+
+	ret = validate_node_names(&valid_nodes);
+	if (ret) {
+		printf("validate_node_names() failed\n");
+		cleanup_node_pattern();
+		return;
+	}
+
+	nb_conf = ethdev_ports_setup();
+
+	ethdev_rxq_configure();
+	ethdev_txq_configure();
+
+	ret = configure_graph_nodes(valid_nodes);
+	if (ret)
+		rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n", ret);
+
+	ret = create_graph(valid_nodes);
+	if (ret)
+		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
+
+	stats = create_graph_cluster_stats();
+	if (stats == NULL)
+		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
+
+	check_all_ports_link_status(enabled_port_mask);
+	start_eth_ports();
+}
+
+cmdline_parse_inst_t cmd_create_graph = {
+	.f = cmd_create_graph_parsed,
+	.data = NULL,
+	.help_str = "create_graph "
+		    "[node_name0,node_name1,node_name2 ... node_nameX]",
+	.tokens = {
+			(void *)&cmd_create_graph_create_graph,
+			(void *)&cmd_create_graph_token_string,
+			NULL,
+		},
+};
+
+/**** Destroy graph ****/
+struct cmd_destroy_graph_result {
+	cmdline_fixed_string_t destroy_graph;
+};
+
+static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =
+	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result, destroy_graph, "destroy_graph");
+
+static void
+cmd_destroy_graph_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			 __rte_unused void *data)
+{
+	uint32_t lcore_id;
+
+	run_graph_walk = false;
+	graph_walk_quit = true;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+		rte_eal_wait_lcore(lcore_id);
+
+	destroy_graph();
+	stop_eth_ports();
+}
+
+cmdline_parse_inst_t cmd_destroy_graph = {
+	.f = cmd_destroy_graph_parsed,
+	.data = NULL,
+	.help_str = "destroy_graph",
+	.tokens = {
+			(void *)&cmd_destroy_graph_destroy_graph,
+			NULL,
+		},
+};
+
+/**** Start graph_walk ****/
+struct cmd_start_graph_walk_result {
+	cmdline_fixed_string_t start;
+	cmdline_fixed_string_t graph_walk;
+};
+
+static cmdline_parse_token_string_t cmd_start_graph_walk_start =
+	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, start, "start");
+static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =
+	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, graph_walk, "graph_walk");
+
+static void
+cmd_start_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	static bool launch_graph_walk;
+
+	if (!launch_graph_walk) {
+		rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
+		launch_graph_walk = true;
+	}
+
+	run_graph_walk = true;
+}
+
+cmdline_parse_inst_t cmd_start_graph_walk = {
+	.f = cmd_start_graph_walk_parsed,
+	.data = NULL,
+	.help_str = "start graph_walk",
+	.tokens = {
+			(void *)&cmd_start_graph_walk_start,
+			(void *)&cmd_start_graph_walk_graph_walk,
+			NULL,
+		},
+};
+
+/**** Stop graph_walk ****/
+struct cmd_stop_graph_walk_result {
+	cmdline_fixed_string_t stop;
+	cmdline_fixed_string_t graph_walk;
+};
+
+static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
+	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, stop, "stop");
+static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =
+	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, graph_walk, "graph_walk");
+
+static void
+cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			   __rte_unused void *data)
+{
+	run_graph_walk = false;
+}
+
+cmdline_parse_inst_t cmd_stop_graph_walk = {
+	.f = cmd_stop_graph_walk_parsed,
+	.data = NULL,
+	.help_str = "stop graph_walk",
+	.tokens = {
+			(void *)&cmd_stop_graph_walk_stop,
+			(void *)&cmd_stop_graph_walk_graph_walk,
+			NULL,
+		},
+};
+
+/**** Show graph_stats ****/
+struct cmd_show_graph_stats_result {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph_stats;
+};
+
+static cmdline_parse_token_string_t cmd_show_graph_stats_show =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, show, "show");
+static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =
+	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, graph_stats, "graph_stats");
+
+static void
+cmd_show_graph_stats_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+			    __rte_unused void *data)
+{
+	if (rte_graph_has_stats_feature()) {
+		if (stats)
+			rte_graph_cluster_stats_get(stats, 0);
+	} else {
+		printf(" graph stats feature not enabled in rte_config.\n");
+	}
+}
+
+cmdline_parse_inst_t cmd_show_graph_stats = {
+	.f = cmd_show_graph_stats_parsed,
+	.data = NULL,
+	.help_str = "show graph_stats",
+	.tokens = {
+			(void *)&cmd_show_graph_stats_show,
+			(void *)&cmd_show_graph_stats_graph_stats,
+			NULL,
+		},
+};
diff --git a/app/test-graph/cmdline_graph.h b/app/test-graph/cmdline_graph.h
new file mode 100644
index 0000000000..2846ff5425
--- /dev/null
+++ b/app/test-graph/cmdline_graph.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef _CMDLINE_GRAPH_H_
+#define _CMDLINE_GRAPH_H_
+
+extern cmdline_parse_inst_t cmd_show_node_list;
+extern cmdline_parse_inst_t cmd_set_lcore_config;
+
+extern cmdline_parse_inst_t cmd_create_graph;
+extern cmdline_parse_inst_t cmd_destroy_graph;
+
+extern cmdline_parse_inst_t cmd_start_graph_walk;
+extern cmdline_parse_inst_t cmd_stop_graph_walk;
+
+extern cmdline_parse_inst_t cmd_show_graph_stats;
+
+#endif /* _CMDLINE_GRAPH_H_ */
diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
new file mode 100644
index 0000000000..d93802a975
--- /dev/null
+++ b/app/test-graph/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(C) 2023 Marvell International Ltd.
+
+# override default name to drop the hyphen
+name = 'test-graph'
+cflags += '-Wno-deprecated-declarations'
+sources = files(
+        'cmdline.c',
+        'cmdline_graph.c',
+        'parameters.c',
+        'testgraph.c',
+)
+
+deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
new file mode 100644
index 0000000000..b990ca4a1c
--- /dev/null
+++ b/app/test-graph/parameters.c
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <getopt.h>
+#include <stdlib.h>
+
+#include "testgraph.h"
+
+static const char short_options[] = "p:" /* portmask */
+				    "P"	 /* promiscuous */
+				    "i"	 /* interactive */
+	;
+
+#define CMD_LINE_OPT_CONFIG	   "config"
+#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
+#define CMD_LINE_OPT_INTERACTIVE   "interactive"
+#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
+#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
+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_CONFIG_NUM,
+	CMD_LINE_OPT_NODE_PATTERN_NUM,
+	CMD_LINE_OPT_INTERACTIVE_NUM,
+	CMD_LINE_OPT_NO_NUMA_NUM,
+	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
+};
+
+static const struct option lgopts[] = {
+	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
+	{CMD_LINE_OPT_NODE_PATTERN, 1, 0, CMD_LINE_OPT_NODE_PATTERN_NUM},
+	{CMD_LINE_OPT_INTERACTIVE, 0, 0, CMD_LINE_OPT_INTERACTIVE_NUM},
+	{CMD_LINE_OPT_NO_NUMA, 0, 0, CMD_LINE_OPT_NO_NUMA_NUM},
+	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0, CMD_LINE_OPT_PARSE_PER_PORT_POOL},
+	{NULL, 0, 0, 0},
+};
+
+/* Display usage */
+static void
+print_usage(const char *prgname)
+{
+	fprintf(stderr,
+		"%s [EAL options] --"
+		" -p PORTMASK"
+		" [-P]"
+		" [-i]"
+		" --config (port,queue,lcore)[,(port,queue,lcore)]"
+		" --node-pattern (node_name0,node_name1[,node_nameX)]"
+		" [--no-numa]"
+		" [--per-port-pool]"
+		" [--interactive]"
+
+		"  -p PORTMASK: Hexadecimal bitmask of ports to configure\n"
+		"  -P : Enable promiscuous mode\n"
+		"  -i : Enter interactive mode\n"
+		"  --config (port,queue,lcore): Rx queue configuration\n"
+		"  --node-pattern (node_names): node patterns to create graph\n"
+		"  --no-numa: Disable numa awareness\n"
+		"  --per-port-pool: Use separate buffer pool per port\n"
+		"  --interactive: Enter interactive mode\n",
+		prgname);
+}
+
+static int
+parse_portmask(const char *portmask)
+{
+	char *end = NULL;
+	unsigned long pm;
+
+	/* Parse hexadecimal string */
+	pm = strtoul(portmask, &end, 16);
+	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
+		return 0;
+
+	return pm;
+}
+
+/* Parse the argument given in the command line of the application */
+int
+parse_cmdline_args(int argc, char **argv)
+{
+	char *prgname = argv[0];
+	int option_index;
+	char **argvopt;
+	int opt, ret;
+
+	argvopt = argv;
+
+	/* Error or normal output strings. */
+	while ((opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index)) != EOF) {
+
+		switch (opt) {
+		/* Portmask */
+		case 'p':
+			enabled_port_mask = parse_portmask(optarg);
+			if (enabled_port_mask == 0) {
+				fprintf(stderr, "Invalid portmask\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+
+		case 'P':
+			promiscuous_on = 1;
+			break;
+
+		/* Long options */
+		case CMD_LINE_OPT_CONFIG_NUM:
+			ret = parse_config(optarg);
+			if (ret) {
+				fprintf(stderr, "Invalid config\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+		case CMD_LINE_OPT_NODE_PATTERN_NUM:
+			ret = parse_node_patterns(optarg);
+			if (ret) {
+				fprintf(stderr, "Invalid node_patterns\n");
+				print_usage(prgname);
+				return -1;
+			}
+			break;
+
+		case CMD_LINE_OPT_INTERACTIVE_NUM:
+		case 'i':
+			printf("Interactive-mode selected\n");
+			interactive = 1;
+			break;
+
+		case CMD_LINE_OPT_NO_NUMA_NUM:
+			numa_on = 0;
+			break;
+
+		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
+			printf("Per port buffer pool is enabled\n");
+			per_port_pool = 1;
+			break;
+
+		default:
+			print_usage(prgname);
+			return -1;
+		}
+	}
+
+	if (optind >= 0)
+		argv[optind - 1] = prgname;
+	ret = optind - 1;
+	optind = 1; /* Reset getopt lib */
+
+	return ret;
+}
diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
new file mode 100644
index 0000000000..aff921acf2
--- /dev/null
+++ b/app/test-graph/testgraph.c
@@ -0,0 +1,1426 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fnmatch.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include <rte_bus.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_eal.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_graph_worker.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_mempool.h>
+#include <rte_per_lcore.h>
+#include <ethdev_rx_priv.h>
+#include <ethdev_tx_priv.h>
+#include <punt_kernel_priv.h>
+#include <kernel_recv_priv.h>
+
+#include "testgraph.h"
+
+/* Log type */
+#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
+
+/*
+ * Configurable number of RX/TX ring descriptors
+ */
+#define RX_DESC_DEFAULT 1024
+#define TX_DESC_DEFAULT 1024
+
+#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
+#define MAX_RX_QUEUE_PER_PORT 128
+
+#define MAX_RX_QUEUE_PER_LCORE 16
+
+#define NB_SOCKETS 8
+
+/* Static global variables used within this file. */
+uint16_t nb_rxd = RX_DESC_DEFAULT;
+uint16_t nb_txd = TX_DESC_DEFAULT;
+
+static volatile bool force_quit;
+volatile bool graph_walk_quit;
+volatile bool run_graph_walk = true;
+
+char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};
+const char **node_patterns;
+uint8_t num_patterns;
+
+uint8_t interactive; /**< interactive mode is off by default */
+int promiscuous_on; /**< Ports set in promiscuous mode off by default. */
+int numa_on = 1;   /**< NUMA is enabled by default. */
+int per_port_pool; /**< Use separate buffer pools per port, disabled by default */
+int testgraph_logtype; /**< Log type for testgraph logs */
+
+struct rte_graph_cluster_stats *stats;
+
+uint32_t enabled_port_mask; /**< Mask of enabled ports */
+
+uint32_t nb_conf;
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+/* Lcore conf */
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
+	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
+	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
+
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+
+struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
+static struct lcore_params lcore_params_array_default[] = {
+	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
+	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
+};
+
+struct lcore_params *lcore_params = lcore_params_array_default;
+uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
+
+static struct rte_eth_conf port_conf = {
+	.rxmode = {
+			.mq_mode = RTE_ETH_MQ_RX_RSS,
+		},
+	.rx_adv_conf = {
+			.rss_conf = {
+					.rss_key = NULL,
+					.rss_hf = RTE_ETH_RSS_IP,
+				},
+		},
+	.txmode = {
+			.mq_mode = RTE_ETH_MQ_TX_NONE,
+		},
+};
+
+static const struct node_list test_node_list[] = {{{"ethdev_rx", "ethdev_tx"}, 0, 2},
+						{{"ethdev_rx", "punt_kernel"}, 0, 2},
+						{{"kernel_recv", "ethdev_tx"}, 0, 2} };
+
+static const struct node_list supported_nodes[] = {{{"ethdev_rx"}, TEST_GRAPH_ETHDEV_RX_NODE, 1},
+						{{"ethdev_tx"}, TEST_GRAPH_ETHDEV_TX_NODE, 1},
+						{{"punt_kernel"}, TEST_GRAPH_PUNT_KERNEL_NODE, 1},
+						{{"kernel_recv"}, TEST_GRAPH_KERNEL_RECV_NODE, 1},
+						{{"ip4_lookup"}, TEST_GRAPH_IP4_LOOKUP_NODE, 1},
+						{{"ip4_rewrite"}, TEST_GRAPH_IP4_REWRITE_NODE, 1},
+						{{"pkt_cls"}, TEST_GRAPH_PKT_CLS_NODE, 1},
+						{{"pkt_drop"}, TEST_GRAPH_PKT_DROP_NODE, 1},
+						{{"NULL"}, TEST_GRAPH_NULL_NODE, 1} };
+
+static struct rte_mempool *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
+
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+
+static int
+check_lcore_params(void)
+{
+	uint8_t queue, lcore;
+	int socketid;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		queue = lcore_params[i].queue_id;
+		if (queue >= MAX_RX_QUEUE_PER_PORT) {
+			printf("Invalid queue number: %hhu\n", queue);
+			return -1;
+		}
+		lcore = lcore_params[i].lcore_id;
+		if (!rte_lcore_is_enabled(lcore)) {
+			printf("Error: lcore %hhu is not enabled in lcore mask\n", lcore);
+			return -1;
+		}
+
+		if (lcore == rte_get_main_lcore()) {
+			printf("Error: lcore %u is main lcore\n", lcore);
+			return -1;
+		}
+		socketid = rte_lcore_to_socket_id(lcore);
+		if ((socketid != 0) && (numa_on == 0)) {
+			printf("Warning: lcore %hhu is on socket %d with numa off\n", lcore,
+			       socketid);
+		}
+	}
+
+	return 0;
+}
+
+static int
+check_port_config(void)
+{
+	uint16_t portid;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		portid = lcore_params[i].port_id;
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("Port %u is not enabled in port mask\n", portid);
+			return -1;
+		}
+		if (!rte_eth_dev_is_valid_port(portid)) {
+			printf("Port %u is not present on the board\n", portid);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static uint8_t
+get_port_n_rx_queues(const uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE, "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+init_lcore_rx_queues(void)
+{
+	uint16_t i, nb_rx_queue;
+	uint8_t lcore;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		lcore = lcore_params[i].lcore_id;
+		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
+		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
+			printf("Error: too many queues (%u) for lcore: %u\n",
+			       (unsigned int)nb_rx_queue + 1, (unsigned int)lcore);
+			return -1;
+		}
+
+		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id = lcore_params[i].port_id;
+		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id = lcore_params[i].queue_id;
+		lcore_conf[lcore].n_rx_queue++;
+	}
+
+	return 0;
+}
+
+int
+validate_config(void)
+{
+	int rc = -1;
+
+	if (check_lcore_params() < 0) {
+		printf("check_lcore_params() failed\n");
+		goto exit;
+	}
+
+	if (init_lcore_rx_queues() < 0) {
+		printf("init_lcore_rx_queues() failed\n");
+		goto exit;
+	}
+
+	if (check_port_config() < 0) {
+		printf("check_port_config() failed\n");
+		goto exit;
+	}
+
+	return 0;
+
+exit:
+	return rc;
+}
+
+#define MEMPOOL_CACHE_SIZE 256
+
+/*
+ * This expression is used to calculate the number of mbufs needed
+ * depending on user input, taking  into account memory for rx and
+ * tx hardware rings, cache per lcore and mtable per port per lcore.
+ * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum
+ * value of 8192
+ */
+#define NB_MBUF(nports)                                                                            \
+	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores * RTE_GRAPH_BURST_SIZE +       \
+		 nports * nb_tx_queue * nb_txd + nb_lcores * MEMPOOL_CACHE_SIZE),                  \
+		8192u)
+
+static int
+init_mem(uint16_t portid, uint32_t nb_mbuf)
+{
+	uint32_t lcore_id;
+	int socketid;
+	char s[64];
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		if (numa_on)
+			socketid = rte_lcore_to_socket_id(lcore_id);
+		else
+			socketid = 0;
+
+		if (socketid >= NB_SOCKETS) {
+			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out of range %d\n",
+				 socketid, lcore_id, NB_SOCKETS);
+		}
+
+		if (pktmbuf_pool[portid][socketid] == NULL) {
+			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid, socketid);
+			/* Create a pool with priv size of a cacheline */
+			pktmbuf_pool[portid][socketid] = rte_pktmbuf_pool_create(
+				s, nb_mbuf, MEMPOOL_CACHE_SIZE, RTE_CACHE_LINE_SIZE,
+				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
+			if (pktmbuf_pool[portid][socketid] == NULL)
+				rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n",
+					 socketid);
+			else
+				printf("Allocated mbuf pool on socket %d\n", socketid);
+		}
+	}
+
+	return 0;
+}
+
+void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int ret;
+	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
+
+	printf("\nChecking link status");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid) {
+			if (force_quit)
+				return;
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+			memset(&link, 0, sizeof(link));
+			ret = rte_eth_link_get_nowait(portid, &link);
+			if (ret < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n", portid,
+					       rte_strerror(-ret));
+				continue;
+			}
+			/* Print link status if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_status_text, sizeof(link_status_text),
+						    &link);
+				printf("Port %d %s\n", portid, link_status_text);
+				continue;
+			}
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+		/* After finally printing all link status, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+	prompt_exit();
+}
+
+int
+graph_main_loop(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit && !graph_walk_quit)) {
+		if (likely(run_graph_walk))
+			rte_graph_walk(graph);
+	}
+
+	return 0;
+}
+
+struct rte_graph_cluster_stats *
+create_graph_cluster_stats(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stat;
+	const char *pattern = "worker_*";
+
+	/* 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;
+
+	stat = rte_graph_cluster_stats_create(&s_param);
+	if (stat == NULL)
+		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
+
+	return stat;
+}
+
+static void
+print_stats(void)
+{
+	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+	const char clr[] = {27, '[', '2', 'J', '\0'};
+
+	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);
+}
+
+int
+parse_config(const char *q_arg)
+{
+	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE, _NUM_FLD };
+	unsigned long int_fld[_NUM_FLD];
+	const char *p, *p0 = q_arg;
+	char *str_fld[_NUM_FLD];
+	uint32_t size;
+	char s[256];
+	char *end;
+	int i;
+
+	nb_lcore_params = 0;
+
+	while ((p = strchr(p0, '(')) != NULL) {
+		++p;
+		p0 = strchr(p, ')');
+		if (p0 == NULL)
+			goto exit;
+
+		size = p0 - p;
+		if (size >= sizeof(s))
+			goto exit;
+
+		memcpy(s, p, size);
+		s[size] = '\0';
+		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD)
+			goto exit;
+		for (i = 0; i < _NUM_FLD; i++) {
+			errno = 0;
+			int_fld[i] = strtoul(str_fld[i], &end, 0);
+			if (errno != 0 || end == str_fld[i])
+				goto exit;
+		}
+
+		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
+			printf("Exceeded max number of lcore params: %hu\n", nb_lcore_params);
+			goto exit;
+		}
+
+		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS || int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
+			printf("Invalid port/lcore id\n");
+			goto exit;
+		}
+
+		lcore_params_array[nb_lcore_params].port_id = (uint8_t)int_fld[FLD_PORT];
+		lcore_params_array[nb_lcore_params].queue_id = (uint8_t)int_fld[FLD_QUEUE];
+		lcore_params_array[nb_lcore_params].lcore_id = (uint8_t)int_fld[FLD_LCORE];
+		++nb_lcore_params;
+	}
+	lcore_params = lcore_params_array;
+
+	return 0;
+exit:
+	/* Revert to default config */
+	lcore_params = lcore_params_array_default;
+	nb_lcore_params = RTE_DIM(lcore_params_array_default);
+
+	return -1;
+}
+
+int
+parse_node_patterns(const char *q_arg)
+{
+	const char *p, *p0 = q_arg;
+	int ret = -EINVAL;
+	uint32_t size;
+
+	num_patterns = 0;
+
+	p = strchr(p0, '(');
+	if (p != NULL) {
+		++p;
+		while ((p0 = strchr(p, ',')) != NULL) {
+			size = p0 - p;
+			if (size >= RTE_NODE_NAMESIZE)
+				goto exit;
+
+			if (num_patterns >= MAX_NODE_PATTERNS) {
+				printf("Too many nodes passed.\n");
+				goto exit;
+			}
+
+			memcpy(node_pattern[num_patterns++], p, size);
+			p = p0 + 1;
+		}
+
+		p0 = strchr(p, ')');
+		if (p0 != NULL) {
+			size = p0 - p;
+			if (size >= RTE_NODE_NAMESIZE)
+				goto exit;
+
+			if (num_patterns >= MAX_NODE_PATTERNS) {
+				printf("Too many nodes passed.\n");
+				goto exit;
+			}
+
+			memcpy(node_pattern[num_patterns++], p, size);
+		} else {
+			goto exit;
+		}
+	} else {
+		goto exit;
+	}
+
+	return 0;
+exit:
+	return ret;
+}
+
+static void
+set_default_node_pattern(void)
+{
+	uint16_t idx;
+
+	for (idx = 0; idx < test_node_list[0].size; idx++)
+		strcpy(node_pattern[num_patterns++], test_node_list[0].nodes[idx]);
+}
+
+int
+validate_node_names(uint64_t *valid_nodes)
+{
+	rte_node_t node_cnt = rte_node_max_count();
+	bool pattern_matched = false;
+	rte_node_t id = 0;
+	int ret = -EINVAL;
+	uint16_t idx, i, j;
+
+	for (idx = 0; idx < num_patterns; idx++) {
+		for (id = 0; id < node_cnt; id++) {
+			if (strncmp(node_pattern[idx], rte_node_id_to_name(id),
+				    RTE_GRAPH_NAMESIZE) == 0)
+				break;
+		}
+		if (node_cnt == id) {
+			printf("Invalid node name passed\n");
+			return ret;
+		}
+	}
+
+	for (i = 0; i < RTE_DIM(test_node_list); i++) {
+		idx = 0;
+		if (test_node_list[i].size == num_patterns) {
+			for (j = 0; j < num_patterns; j++) {
+				if (strncmp(node_pattern[j], test_node_list[i].nodes[j],
+				    RTE_GRAPH_NAMESIZE) == 0)
+					idx++;
+			}
+			if (idx == num_patterns)
+				pattern_matched = true;
+		}
+	}
+
+	if (!pattern_matched) {
+		printf("Unsupported node pattern passed\n\n");
+		printf("Test supported node patterns are:\n");
+		for (i = 0; i < RTE_DIM(test_node_list); i++) {
+			printf("(");
+			for (j = 0; j < (test_node_list[i].size - 1); j++)
+				printf("%s,", test_node_list[i].nodes[j]);
+			printf("%s", test_node_list[i].nodes[j]);
+			printf(")\n");
+		}
+
+		return ret;
+	}
+
+	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
+		for (j = 0; j < num_patterns; j++) {
+			if (strncmp(node_pattern[j], supported_nodes[i].nodes[0],
+				    RTE_GRAPH_NAMESIZE) == 0) {
+				*valid_nodes |= supported_nodes[i].test_id;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+void
+cleanup_node_pattern(void)
+{
+	while (num_patterns) {
+		memset(node_pattern[num_patterns - 1], 0, RTE_GRAPH_NAMESIZE);
+		num_patterns--;
+	}
+}
+
+static int
+ethdev_tx_node_configure(void)
+{
+	struct ethdev_tx_node_main *tx_node_data;
+	struct rte_node_register *tx_node;
+	char name[RTE_NODE_NAMESIZE];
+	uint16_t port_id;
+	uint32_t id;
+
+	tx_node_data = ethdev_tx_node_data_get();
+	tx_node = ethdev_tx_node_get();
+
+	RTE_ETH_FOREACH_DEV(port_id) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << port_id)) == 0) {
+			printf("\nSkipping disabled port %d\n", port_id);
+			continue;
+		}
+
+		if (!rte_eth_dev_is_valid_port(port_id))
+			return -EINVAL;
+
+		/* Create a per port tx node from base node */
+		snprintf(name, sizeof(name), "%u", port_id);
+		id = rte_node_clone(tx_node->id, name);
+		tx_node_data->nodes[port_id] = id;
+
+		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node->name, name, id);
+	}
+
+	return 0;
+}
+
+static int
+punt_kernel_node_configure(void)
+{
+	struct rte_node_register *punt_node;
+	char name[RTE_NODE_NAMESIZE];
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+	uint32_t id;
+
+	punt_node = punt_kernel_node_get();
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Create a per lcore punt_kernel node from base node */
+		snprintf(name, sizeof(name), "%u", lcore_id);
+		id = rte_node_clone(punt_node->id, name);
+		strcpy(qconf->punt_kernel_node_name, rte_node_id_to_name(id));
+
+		printf("punt_kernel node %s-%s: is at %u\n", punt_node->name, name, id);
+	}
+
+	return 0;
+}
+
+static int
+kernel_recv_node_configure(void)
+{
+	struct rte_node_register *recv_node;
+	char name[RTE_NODE_NAMESIZE];
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+	uint32_t id;
+
+	recv_node = kernel_recv_node_get();
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Create a per lcore kernel_recv node from base node */
+		snprintf(name, sizeof(name), "%u", lcore_id);
+		id = rte_node_clone(recv_node->id, name);
+		strcpy(qconf->kernel_recv_node_name, rte_node_id_to_name(id));
+
+		printf("kernel_recv node %s-%s: is at %u\n", recv_node->name, name, id);
+	}
+
+	return 0;
+}
+
+static int
+ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
+{
+	char name[RTE_NODE_NAMESIZE];
+	uint16_t i, j, port_id;
+	uint32_t id;
+
+	for (i = 0; i < nb_confs; i++) {
+		port_id = conf[i].port_id;
+
+		if (!rte_eth_dev_is_valid_port(port_id))
+			return -EINVAL;
+
+		/* Create node for each rx port queue pair */
+		for (j = 0; j < conf[i].num_rx_queues; j++) {
+			struct ethdev_rx_node_main *rx_node_data;
+			struct rte_node_register *rx_node;
+			ethdev_rx_node_elem_t *elem;
+
+			rx_node_data = ethdev_rx_get_node_data_get();
+			rx_node = ethdev_rx_node_get();
+			snprintf(name, sizeof(name), "%u-%u", port_id, j);
+			/* Clone a new rx node with same edges as parent */
+			id = rte_node_clone(rx_node->id, name);
+			if (id == RTE_NODE_ID_INVALID)
+				return -EIO;
+
+			/* Add it to list of ethdev rx nodes for lookup */
+			elem = malloc(sizeof(ethdev_rx_node_elem_t));
+			if (elem == NULL)
+				return -ENOMEM;
+			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
+			elem->ctx.port_id = port_id;
+			elem->ctx.queue_id = j;
+			elem->nid = id;
+			elem->next = rx_node_data->head;
+			rx_node_data->head = elem;
+
+			printf("ethdev:: Rx node %s-%s: is at %u\n", rx_node->name, name, id);
+		}
+	}
+
+	return 0;
+}
+
+static int
+update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
+{
+	struct ethdev_rx_node_main *rx_node_data;
+	ethdev_rx_node_elem_t *elem;
+	char *next_nodes[16];
+	rte_edge_t count;
+	uint16_t i;
+
+	count = rte_node_edge_count(id);
+	rte_node_edge_get(id, next_nodes);
+
+	for (i = 0; i < count; i++) {
+		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
+			rx_node_data = ethdev_rx_get_node_data_get();
+			elem = rx_node_data->head;
+			while (elem->next != rx_node_data->head) {
+				if (elem->nid == id)
+					break;
+				elem = elem->next;
+			}
+
+			if (elem->nid == id)
+				elem->ctx.cls_next = i;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int
+link_ethdev_rx_to_tx_node(uint32_t lcore_id)
+{
+	const char * const pattern[] = {"ethdev_tx-*"};
+	uint16_t queue, queue_id, port_id;
+	char name[RTE_NODE_NAMESIZE];
+	const char *next_node = name;
+	struct lcore_conf *qconf;
+	uint32_t rx_id;
+
+	qconf = &lcore_conf[lcore_id];
+
+	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+		port_id = qconf->rx_queue_list[queue].port_id;
+		queue_id = qconf->rx_queue_list[queue].queue_id;
+
+		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id, queue_id);
+		rx_id = rte_node_from_name(name);
+
+		/* Fill node pattern */
+		strcpy(node_pattern[num_patterns++], name);
+
+		/* Prepare the actual name of the cloned node */
+		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
+
+		/* Update ethdev_rx node edges */
+		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+		/* Fill node pattern */
+		strcpy(node_pattern[num_patterns++], name);
+
+		/* Update node_next details */
+		update_ethdev_rx_node_next(rx_id, pattern[0]);
+	}
+
+	return 0;
+}
+
+static int
+link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
+{
+	const char * const pattern[] = {"punt_kernel-*"};
+	uint16_t queueid, portid, queue;
+	char name[RTE_NODE_NAMESIZE];
+	const char *next_node = name;
+	struct lcore_conf *qconf;
+	rte_node_t rx_id;
+
+	qconf = &lcore_conf[lcore_id];
+
+	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+
+		portid = qconf->rx_queue_list[queue].port_id;
+		queueid = qconf->rx_queue_list[queue].queue_id;
+
+		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid, queueid);
+		rx_id = rte_node_from_name(name);
+
+		/* Fill node pattern */
+		strcpy(node_pattern[num_patterns++], name);
+
+		next_node = qconf->punt_kernel_node_name;
+
+		/* Fill node pattern */
+		strcpy(node_pattern[num_patterns++], next_node);
+
+		/* Update ethdev_rx node edges */
+		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+		/* Update node_next details */
+		update_ethdev_rx_node_next(rx_id, pattern[0]);
+	}
+
+	return 0;
+}
+
+static int
+update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
+{
+	struct kernel_recv_node_main *rx_node_data;
+	kernel_recv_node_elem_t *elem;
+	char *next_nodes[16];
+	rte_edge_t count;
+	uint16_t i;
+
+	count = rte_node_edge_count(id);
+	rte_node_edge_get(id, next_nodes);
+
+	for (i = 0; i < count; i++) {
+		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
+			rx_node_data = kernel_recv_node_data_get();
+			elem = rx_node_data->head;
+			while (elem->next != rx_node_data->head) {
+				if (elem->nid == id)
+					break;
+				elem = elem->next;
+			}
+
+			if (elem->nid == id)
+				elem->ctx.recv_info->cls_next = i;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int
+link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
+{
+	const char * const pattern[] = {"ethdev_tx-*"};
+	char name[RTE_NODE_NAMESIZE];
+	const char *next_node = name;
+	struct lcore_conf *qconf;
+	uint16_t port_id;
+	rte_node_t id;
+
+	qconf = &lcore_conf[lcore_id];
+
+	id = rte_node_from_name(qconf->kernel_recv_node_name);
+
+	/* Fill node pattern */
+	strcpy(node_pattern[num_patterns++], qconf->kernel_recv_node_name);
+
+	port_id = lcore_id;
+
+	if ((enabled_port_mask & (1 << port_id)) == 0) {
+		/* Use available port_id */
+		RTE_ETH_FOREACH_DEV(port_id) {
+			if ((enabled_port_mask & (1 << port_id)) != 0)
+				break;
+		}
+	}
+
+	if (!rte_eth_dev_is_valid_port(port_id)) {
+		printf("Port %u is not present on the board\n", port_id);
+		return -1;
+	}
+
+	/* Prepare the actual name of the cloned node */
+	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
+
+	/* Update ethdev_rx node edges */
+	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+	/* Fill node pattern */
+	strcpy(node_pattern[num_patterns++], name);
+
+	/* Update node_next details */
+	update_kernel_recv_node_next(id, pattern[0]);
+
+	return 0;
+}
+
+uint32_t
+ethdev_ports_setup(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint32_t nb_tx_queue, nb_lcores;
+	uint32_t nb_ports, nb_conf = 0;
+	uint8_t nb_rx_queue;
+	uint16_t portid;
+	int ret;
+
+	nb_ports = rte_eth_dev_count_avail();
+	nb_lcores = rte_lcore_count();
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		struct rte_eth_conf local_port_conf = port_conf;
+
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		/* Init port */
+		printf("Initializing port %d ... ", portid);
+		fflush(stdout);
+
+		nb_rx_queue = get_port_n_rx_queues(portid);
+		nb_tx_queue = nb_lcores;
+		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
+			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
+		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n", nb_rx_queue, nb_tx_queue);
+
+		rte_eth_dev_info_get(portid, &dev_info);
+
+		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 !=
+		    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, 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_rx_queue, nb_tx_queue, &local_port_conf);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%d\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=%d\n",
+				 ret, portid);
+
+		/* Init memory */
+		if (!per_port_pool)
+			ret = init_mem(0, NB_MBUF(nb_ports));
+		else
+			ret = init_mem(portid, NB_MBUF(1));
+
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
+		if (!per_port_pool)
+			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
+
+		else
+			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
+		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
+
+		nb_conf++;
+	}
+
+	return nb_conf;
+}
+
+void
+ethdev_rxq_configure(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t queueid, portid;
+	struct lcore_conf *qconf;
+	uint8_t queue, socketid;
+	uint32_t lcore_id;
+	int ret;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			struct rte_eth_rxconf rxq_conf;
+
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			if (numa_on)
+				socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
+			else
+				socketid = 0;
+
+			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
+			fflush(stdout);
+
+			rte_eth_dev_info_get(portid, &dev_info);
+			rxq_conf = dev_info.default_rxconf;
+			rxq_conf.offloads = port_conf.rxmode.offloads;
+			if (!per_port_pool)
+				ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
+							     &rxq_conf, pktmbuf_pool[0][socketid]);
+			else
+				ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
+							     &rxq_conf,
+							     pktmbuf_pool[portid][socketid]);
+			if (ret < 0)
+				rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup: err=%d, port=%d\n",
+					 ret, portid);
+
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+	}
+	printf("\n");
+}
+
+void
+ethdev_txq_configure(void)
+{
+	struct rte_eth_dev_info dev_info;
+	struct rte_eth_txconf *txconf;
+	uint16_t queueid, portid;
+	uint32_t lcore_id;
+	uint8_t socketid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		struct rte_eth_conf local_port_conf = port_conf;
+
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		rte_eth_dev_info_get(portid, &dev_info);
+
+		/* Init one TX queue per (lcore,port) pair */
+		queueid = 0;
+		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+			if (rte_lcore_is_enabled(lcore_id) == 0)
+				continue;
+
+			if (numa_on)
+				socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
+			else
+				socketid = 0;
+
+			printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
+			fflush(stdout);
+
+			txconf = &dev_info.default_txconf;
+			txconf->offloads = local_port_conf.txmode.offloads;
+			ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd, socketid, txconf);
+			if (ret < 0)
+				rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: err=%d, port=%d\n",
+					 ret, portid);
+			queueid++;
+		}
+	}
+	printf("\n");
+}
+
+int
+configure_graph_nodes(uint64_t valid_nodes)
+{
+	int ret = 0;
+
+	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
+		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
+		if (ret) {
+			printf("ethdev_rx_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
+		ret = ethdev_tx_node_configure();
+		if (ret) {
+			printf("ethdev_tx_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
+		ret = punt_kernel_node_configure();
+		if (ret) {
+			printf("punt_kernel_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
+		ret = kernel_recv_node_configure();
+		if (ret) {
+			printf("kernel_recv_node_configure: err=%d\n", ret);
+			goto exit;
+		}
+	}
+
+exit:
+	return ret;
+}
+
+static int
+link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
+{
+	int ret = 0;
+
+	num_patterns = 0;
+
+	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_ETHDEV_RX_NODE)) {
+		ret = link_ethdev_rx_to_tx_node(lcore_id);
+		if (ret) {
+			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
+			goto exit;
+		}
+	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE | TEST_GRAPH_PUNT_KERNEL_NODE)) {
+		link_ethdev_rx_to_punt_kernel_node(lcore_id);
+	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_KERNEL_RECV_NODE)) {
+		link_kernel_recv_to_ethdev_tx_node(lcore_id);
+	} else {
+		printf("Invalid node map\n");
+		ret = -EINVAL;
+	}
+
+exit:
+	return ret;
+}
+
+void
+start_eth_ports(void)
+{
+	uint16_t portid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		ret = rte_eth_dev_start(portid);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", ret, portid);
+
+		if (promiscuous_on)
+			rte_eth_promiscuous_enable(portid);
+	}
+}
+
+void
+stop_eth_ports(void)
+{
+	uint16_t portid;
+	int ret;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		ret = rte_eth_dev_stop(portid);
+		if (ret != 0)
+			printf("Failed to stop port %u: %s\n", portid, rte_strerror(-ret));
+		rte_eth_dev_close(portid);
+	}
+}
+
+int
+create_graph(uint64_t valid_nodes)
+{
+	struct rte_graph_param graph_conf;
+	struct lcore_conf *qconf;
+	uint32_t lcore_id;
+	int ret;
+
+	node_patterns = malloc(MAX_NODE_PATTERNS * sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		ret = link_graph_nodes(valid_nodes, lcore_id);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "link_graph_nodes(): failed\n");
+
+		for (i = 0; i < num_patterns; i++) {
+			graph_conf.node_patterns[i] = node_pattern[i];
+			printf("%s,", graph_conf.node_patterns[i]);
+		}
+		printf("\n");
+
+		graph_conf.nb_node_patterns = num_patterns;
+
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u", lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE, "rte_graph_create(): graph_id invalid for lcore%u\n",
+				 lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph %s not found\n",
+				 qconf->name);
+	}
+
+	return 0;
+}
+
+int
+destroy_graph(void)
+{
+	uint32_t lcore_id;
+	rte_graph_t id;
+	int ret;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+	{
+		if (lcore_conf[lcore_id].graph) {
+			id = rte_graph_from_name(lcore_conf[lcore_id].name);
+			if (rte_graph_destroy(id)) {
+				printf("graph_id %u destroy failed.\n", id);
+				ret = -1;
+			}
+		}
+	}
+
+	if (node_patterns)
+		free(node_patterns);
+
+	return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+	uint64_t valid_nodes;
+	uint32_t lcore_id;
+	int ret;
+
+	graph_walk_quit = false;
+	force_quit = false;
+	interactive = 0;
+
+	node_patterns = NULL;
+
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	testgraph_logtype = rte_log_register("testgraph");
+	if (testgraph_logtype < 0)
+		rte_exit(EXIT_FAILURE, "Cannot register log type");
+
+	set_default_node_pattern();
+
+	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
+
+	ret = rte_eal_init(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n", rte_strerror(rte_errno));
+	argc -= ret;
+	argv += ret;
+
+	if (argc > 1) {
+		ret = parse_cmdline_args(argc, argv);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Invalid command line parameters\n");
+	}
+
+#ifdef RTE_LIB_CMDLINE
+	if (init_cmdline() != 0)
+		rte_exit(EXIT_FAILURE, "Could not initialise cmdline context.\n");
+
+	if (interactive == 1) {
+		prompt();
+	} else
+#endif
+	{
+		if (validate_config() < 0)
+			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
+
+		ret = validate_node_names(&valid_nodes);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "validate_node_names: err=%d\n", ret);
+
+		nb_conf = ethdev_ports_setup();
+
+		ethdev_rxq_configure();
+
+		ethdev_txq_configure();
+
+		ret = configure_graph_nodes(valid_nodes);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n", ret);
+
+		ret = create_graph(valid_nodes);
+		if (ret)
+			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
+
+		stats = create_graph_cluster_stats();
+		if (stats == NULL)
+			rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
+
+		check_all_ports_link_status(enabled_port_mask);
+
+		start_eth_ports();
+
+		/* Launch per-lcore init on every worker lcore */
+		rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
+
+		/* Accumulate and print stats on main until exit */
+		if (rte_graph_has_stats_feature())
+			print_stats();
+
+		/* Wait for worker cores to exit */
+		RTE_LCORE_FOREACH_WORKER(lcore_id) {
+			ret = rte_eal_wait_lcore(lcore_id);
+			if (ret < 0)
+				break;
+		}
+
+		ret = destroy_graph();
+
+		stop_eth_ports();
+	}
+
+	/* clean up the EAL */
+	ret = rte_eal_cleanup();
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-ret));
+
+	return EXIT_SUCCESS;
+}
diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
new file mode 100644
index 0000000000..ea1f8ef4fa
--- /dev/null
+++ b/app/test-graph/testgraph.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef _TESTGRAPH_H_
+#define _TESTGRAPH_H_
+
+#include <stdbool.h>
+
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#include <cmdline.h>
+#include <cmdline_parse.h>
+
+
+#define MAX_LCORE_PARAMS 1024
+#define MAX_NODE_PATTERNS 128
+
+#ifndef BIT_ULL
+#define BIT_ULL(nr) (1ULL << (nr))
+#endif
+
+#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
+#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
+#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
+#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
+#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
+#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
+#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
+#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
+#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
+
+static volatile bool force_quit;
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern uint32_t enabled_port_mask;
+extern uint32_t nb_conf;
+
+extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by default. */
+extern uint8_t interactive;	   /**< interactive mode is disabled by default. */
+extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
+extern int numa_on;		   /**< NUMA is enabled by default. */
+extern int per_port_pool;
+
+extern volatile bool graph_walk_quit;
+extern volatile bool run_graph_walk;
+extern struct rte_graph_cluster_stats *stats;
+
+extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
+extern uint8_t num_patterns;
+
+struct node_list {
+	const char *nodes[MAX_NODE_PATTERNS];
+	uint64_t test_id;
+	uint8_t size;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+void prompt(void);
+void prompt_exit(void);
+int init_cmdline(void);
+int validate_config(void);
+int parse_cmdline_args(int argc, char **argv);
+uint32_t ethdev_ports_setup(void);
+void ethdev_rxq_configure(void);
+void ethdev_txq_configure(void);
+void start_eth_ports(void);
+void stop_eth_ports(void);
+int create_graph(uint64_t valid_nodes);
+int destroy_graph(void);
+int graph_main_loop(void *conf);
+struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
+void check_all_ports_link_status(uint32_t port_mask);
+int configure_graph_nodes(uint64_t valid_nodes);
+
+int parse_config(const char *q_arg);
+int parse_node_patterns(const char *q_arg);
+int validate_node_names(uint64_t *valid_nodes);
+void cleanup_node_pattern(void);
+
+#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
+	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt, ##args)
+
+#endif /* _TESTGRAPH_H_ */
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index 6f84fc31ff..f18c508fa2 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -20,6 +20,7 @@ DPDK Tools User Guides
     cryptoperf
     comp_perf
     testeventdev
+    testgraph
     testregex
     testmldev
     dts
diff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst
new file mode 100644
index 0000000000..3c1e058724
--- /dev/null
+++ b/doc/guides/tools/testgraph.rst
@@ -0,0 +1,131 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(C) 2023 Marvell International Ltd.
+
+dpdk-test-graph Application
+===========================
+
+The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK) application that allows
+exercising various graph library features. This application has a generic framework to add
+new test configurations and expand test coverage to verify the functionality of graph nodes
+and observe the graph cluster statistics.
+
+Running the Application
+-----------------------
+
+The application has a number of command line options:
+
+.. code-block:: console
+
+   dpdk-test-eventdev [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+The following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-test-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bitmask of the cores to run on. The corelist is a
+        list of cores to use.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+The following are the application command-line options:
+
+* ``-p <n>``
+
+        Set the ethdev port mask.
+
+* ``-P``
+
+        Set the ethdev ports in promiscuous mode.
+
+* ``--config <config>``
+
+        Set the Rxq configuration.
+        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
+
+* ``--node-pattern <n>``
+
+        Set the node patterns to use in graph creation.
+        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
+
+* ``--per-port-pool``
+
+        Use separate buffer pool per port.
+
+* ``--no-numa``
+
+        Disable numa awareness.
+
+* ``--interactive``
+
+        Switch to interactive mode.
+
+Running the Tool
+~~~~~~~~~~~~~~~~
+
+Here is the sample command line to run simple iofwd test::
+
+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
+       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
+
+Below is a sample command line to punt rx packets to kernel::
+
+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
+       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
+
+Interactive mode
+~~~~~~~~~~~~~~~~
+
+Tool uses ``--interactive`` command line option to enter interactive mode and use cmdline options
+to setup the required node configurations, create graph and than start graph_walk.
+
+
+testgraph> help
+
+Help is available for the following sections:
+
+    help control                    : Start and stop graph walk.
+    help display                    : Displaying port, stats and config information.
+    help config                     : Configuration information.
+    help all                        : All of the above sections.
+
+testgraph> help all
+
+Control forwarding:
+
+start graph_walk
+ Start graph_walk on worker threads.
+
+stop graph_walk
+ Stop worker threads from running graph_walk.
+
+quit
+ Quit to prompt.
+
+
+Display:
+
+show node_list
+ Display the list of supported nodes.
+
+show graph_stats
+ Display the node statistics of graph cluster.
+
+
+Configuration:
+
+set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
+ Set lcore configuration.
+
+create_graph (node0_name,node1_name,...,nodeX_name)
+ Create graph instances using the provided node details.
+
+destroy_graph
+ Destroy the graph instances.
+
+testgraph>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v2 4/4] app: add testgraph application
  2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
@ 2023-05-09  2:34     ` Sunil Kumar Kori
  2023-05-09  3:39       ` Vamsi Krishna Attunuru
  2023-05-12 11:08     ` Yan, Zhirun
  2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
  2 siblings, 1 reply; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-05-09  2:34 UTC (permalink / raw)
  To: Vamsi Krishna Attunuru, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Vamsi Krishna Attunuru, Nithin Kumar Dabilpuram

Is there any user guide similar to testpmd ?

> -----Original Message-----
> From: Vamsi Attunuru <vattunuru@marvell.com>
> Sent: Tuesday, April 25, 2023 6:45 PM
> To: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> <jerinj@marvell.com>
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> Dabilpuram <ndabilpuram@marvell.com>
> Subject: [EXT] [PATCH v2 4/4] app: add testgraph application
> 
> External Email
> 
> ----------------------------------------------------------------------
> Patch adds test-graph application to validate graph
> and node libraries.
> 
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  app/meson.build                |    1 +
>  app/test-graph/cmdline.c       |  211 +++++
>  app/test-graph/cmdline_graph.c |  294 +++++++
>  app/test-graph/cmdline_graph.h |   19 +
>  app/test-graph/meson.build     |   14 +
>  app/test-graph/parameters.c    |  157 ++++
>  app/test-graph/testgraph.c     | 1426 ++++++++++++++++++++++++++++++++
>  app/test-graph/testgraph.h     |   91 ++
>  doc/guides/tools/index.rst     |    1 +
>  doc/guides/tools/testgraph.rst |  131 +++
>  10 files changed, 2345 insertions(+)
> 
> diff --git a/app/meson.build b/app/meson.build
> index 74d2420f67..6c7b24e604 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -22,6 +22,7 @@ apps = [
>          'test-eventdev',
>          'test-fib',
>          'test-flow-perf',
> +        'test-graph',
>          'test-gpudev',
>          'test-mldev',
>          'test-pipeline',
> diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> new file mode 100644
> index 0000000000..d9474d827a
> --- /dev/null
> +++ b/app/test-graph/cmdline.c
> @@ -0,0 +1,211 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */

Please check copyright. I think  it should be " Copyright(C) 2023 Marvell." Only.
> +
> +#include <stdlib.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_etheraddr.h>
> +#include <cmdline_parse_ipaddr.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_rdline.h>
> +#include <cmdline_socket.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +static struct cmdline *testgraph_cl;
> +static cmdline_parse_ctx_t *main_ctx;
> +
> +/* *** Help command with introduction. *** */
> +struct cmd_help_brief_result {
> +	cmdline_fixed_string_t help;
> +};
> +
> +static void
> +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline
> *cl, __rte_unused void *data)
> +{
> +	cmdline_printf(cl,
> +		       "\n"
> +		       "Help is available for the following sections:\n\n"
> +		       "    help control                    : Start and stop graph walk.\n"
> +		       "    help display                    : Displaying port, stats and
> config "
> +		       "information.\n"
> +		       "    help config                     : Configuration information.\n"
> +		       "    help all                        : All of the above sections.\n\n");
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_brief_help =
> +	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help,
> "help");
> +
> +static cmdline_parse_inst_t cmd_help_brief = {
> +	.f = cmd_help_brief_parsed,
> +	.data = NULL,
> +	.help_str = "help: Show help",
> +	.tokens = {
> +			(void *)&cmd_help_brief_help,
> +			NULL,
> +		},
> +};
> +
> +/* *** Help command with help sections. *** */
> +struct cmd_help_long_result {
> +	cmdline_fixed_string_t help;
> +	cmdline_fixed_string_t section;
> +};
> +
> +static void
> +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl,
> __rte_unused void *data)
> +{
> +	int show_all = 0;
> +	struct cmd_help_long_result *res = parsed_result;
> +
> +	if (!strcmp(res->section, "all"))
> +		show_all = 1;
> +
> +	if (show_all || !strcmp(res->section, "control")) {
> +
> +		cmdline_printf(cl, "\n"
> +				   "Control forwarding:\n"
> +				   "-------------------\n\n"
> +
> +				   "start graph_walk\n"
> +				   " Start graph_walk on worker threads.\n\n"
> +
> +				   "stop graph_walk\n"
> +				   " Stop worker threads from running
> graph_walk.\n\n"
> +
> +				   "quit\n"
> +				   "    Quit to prompt.\n\n");
> +	}
> +
> +	if (show_all || !strcmp(res->section, "display")) {
> +
> +		cmdline_printf(cl,
> +			       "\n"
> +			       "Display:\n"
> +			       "--------\n\n"
> +
> +			       "show node_list\n"
> +			       " Display the list of supported nodes.\n\n"
> +
> +			       "show graph_stats\n"
> +			       " Display the node statistics of graph
> cluster.\n\n");
> +	}
> +
> +	if (show_all || !strcmp(res->section, "config")) {
> +		cmdline_printf(cl, "\n"
> +				   "Configuration:\n"
> +				   "--------------\n"
> +				   "set lcore_config
> (port_id0,rxq0,lcore_idX),..."
> +				   ".....,(port_idX,rxqX,lcoreidY)\n"
> +				   " Set lcore configuration.\n\n"
> +
> +				   "create_graph
> (node0_name,node1_name,...,nodeX_name)\n"
> +				   " Create graph instances using the provided
> node details.\n\n"
> +
> +				   "destroy_graph\n"
> +				   " Destroy the graph instances.\n\n");
> +	}
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_long_help =
> +	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help,
> "help");
> +
> +static cmdline_parse_token_string_t cmd_help_long_section =
> TOKEN_STRING_INITIALIZER(
> +	struct cmd_help_long_result, section, "all#control#display#config");
> +
> +static cmdline_parse_inst_t cmd_help_long = {
> +	.f = cmd_help_long_parsed,
> +	.data = NULL,
> +	.help_str = "help all|control|display|config: "
> +		    "Show help",
> +	.tokens = {
> +			(void *)&cmd_help_long_help,
> +			(void *)&cmd_help_long_section,
> +			NULL,
> +		},
> +};
> +
> +/* *** QUIT *** */
> +struct cmd_quit_result {
> +	cmdline_fixed_string_t quit;
> +};
> +
> +static void
> +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> __rte_unused void *data)
> +{
> +	cmdline_quit(cl);
> +}
> +
> +static cmdline_parse_token_string_t cmd_quit_quit =
> +	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> +
> +static cmdline_parse_inst_t cmd_quit = {
> +	.f = cmd_quit_parsed,
> +	.data = NULL,
> +	.help_str = "quit: Exit application",
> +	.tokens = {
> +			(void *)&cmd_quit_quit,
> +			NULL,
> +		},
> +};
> +
> +/* list of instructions */
> +static cmdline_parse_ctx_t builtin_ctx[] = {
> +	(cmdline_parse_inst_t *)&cmd_help_brief,
> +	(cmdline_parse_inst_t *)&cmd_help_long,
> +	(cmdline_parse_inst_t *)&cmd_quit,
> +	(cmdline_parse_inst_t *)&cmd_show_node_list,
> +	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
> +	(cmdline_parse_inst_t *)&cmd_create_graph,
> +	(cmdline_parse_inst_t *)&cmd_destroy_graph,
> +	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
> +	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> +	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
> +	NULL,
> +};
> +
> +int
> +init_cmdline(void)
> +{
> +	unsigned int count;
> +	unsigned int i;
> +
> +	count = 0;
> +	for (i = 0; builtin_ctx[i] != NULL; i++)
> +		count++;
> +
> +	/* cmdline expects a NULL terminated array */
> +	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> +	if (main_ctx == NULL)
> +		return -1;
> +
> +	count = 0;
> +	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> +		main_ctx[count] = builtin_ctx[i];
> +
> +	return 0;
> +}
> +
> +void
> +prompt_exit(void)
> +{
> +	cmdline_quit(testgraph_cl);
> +}
> +
> +/* prompt function, called from main on MAIN lcore */
> +void
> +prompt(void)
> +{
> +	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> +	if (testgraph_cl == NULL) {
> +		fprintf(stderr, "Failed to create stdin based cmdline
> context\n");
> +		return;
> +	}
> +
> +	cmdline_interact(testgraph_cl);
> +	cmdline_stdin_exit(testgraph_cl);
> +}
> diff --git a/app/test-graph/cmdline_graph.c b/app/test-
> graph/cmdline_graph.c
> new file mode 100644
> index 0000000000..d66149a224
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.c
> @@ -0,0 +1,294 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +/* *** Show supported node details *** */
> +struct cmd_show_node_list_result {
> +	cmdline_fixed_string_t show;
> +	cmdline_fixed_string_t node_list;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_node_list_show =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> show, "show");
> +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> node_list, "node_list");
> +
> +static void
> +cmd_show_node_list_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			  __rte_unused void *data)
> +{
> +	rte_node_t node_cnt = rte_node_max_count();
> +	rte_node_t id;
> +
> +	printf("\n**** Supported Graph Nodes ****\n");
> +	for (id = 0; id < node_cnt; id++)
> +		printf("%s\n", rte_node_id_to_name(id));
> +
> +	printf("********************************\n");
> +}
> +
> +cmdline_parse_inst_t cmd_show_node_list = {
> +	.f = cmd_show_node_list_parsed,
> +	.data = NULL,
> +	.help_str = "show node_list",
> +	.tokens = {
> +			(void *)&cmd_show_node_list_show,
> +			(void *)&cmd_show_node_list_node_list,
> +			NULL,
> +		},
> +};
> +
> +/* *** Set lcore config *** */
> +struct cmd_set_lcore_config_result {
> +	cmdline_fixed_string_t set;
> +	cmdline_fixed_string_t lcore_config;
> +	cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set,
> "set");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =
> +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> lcore_config, "lcore_config");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string =
> TOKEN_STRING_INITIALIZER(
> +	struct cmd_set_lcore_config_result, token_string,
> TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct
> cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	struct cmd_set_lcore_config_result *res = parsed_result;
> +	const char *t_str = res->token_string;
> +	int ret;
> +
> +	/* Parse string */
> +	ret = parse_config(t_str);
> +	if (ret) {
> +		printf(" lcore_config string parse error\n");
> +		return;
> +	}
> +
> +	validate_config();
> +}
> +
> +cmdline_parse_inst_t cmd_set_lcore_config = {
> +	.f = cmd_set_lcore_config_parsed,
> +	.data = NULL,
> +	.help_str = "set lcore_config "
> +		    "(port,queue,lcore),[(port,queue,lcore) ...
> (port,queue,lcore)]",
> +	.tokens = {
> +			(void *)&cmd_set_lcore_config_set,
> +			(void *)&cmd_set_lcore_config_lcore_config,
> +			(void *)&cmd_set_lcore_config_token_string,
> +			NULL,
> +		},
> +};
> +
> +/* *** Create graph *** */
> +struct cmd_create_graph_result {
> +	cmdline_fixed_string_t create_graph;
> +	cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> create_graph, "create_graph");
> +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> token_string, TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct
> cmdline *cl,
> +			__rte_unused void *data)
> +{
> +	struct cmd_create_graph_result *res = parsed_result;
> +	const char *t_str = res->token_string;
> +	uint64_t valid_nodes = 0;
> +	int ret;
> +
> +	ret = parse_node_patterns(t_str);
> +	if (ret) {
> +		printf("parse_node_patterns failed\n");
> +		cleanup_node_pattern();
> +		return;
> +	}
> +
> +	ret = validate_node_names(&valid_nodes);
> +	if (ret) {
> +		printf("validate_node_names() failed\n");
> +		cleanup_node_pattern();
> +		return;
> +	}
> +
> +	nb_conf = ethdev_ports_setup();
> +
> +	ethdev_rxq_configure();
> +	ethdev_txq_configure();
> +
> +	ret = configure_graph_nodes(valid_nodes);
> +	if (ret)
> +		rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n",
> ret);
> +
> +	ret = create_graph(valid_nodes);
> +	if (ret)
> +		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> +
> +	stats = create_graph_cluster_stats();
> +	if (stats == NULL)
> +		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> failed\n");
> +
> +	check_all_ports_link_status(enabled_port_mask);
> +	start_eth_ports();
> +}
> +
> +cmdline_parse_inst_t cmd_create_graph = {
> +	.f = cmd_create_graph_parsed,
> +	.data = NULL,
> +	.help_str = "create_graph "
> +		    "[node_name0,node_name1,node_name2 ...
> node_nameX]",
> +	.tokens = {
> +			(void *)&cmd_create_graph_create_graph,
> +			(void *)&cmd_create_graph_token_string,
> +			NULL,
> +		},
> +};
> +
> +/**** Destroy graph ****/
> +struct cmd_destroy_graph_result {
> +	cmdline_fixed_string_t destroy_graph;
> +};
> +
> +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =
> +	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result,
> destroy_graph, "destroy_graph");
> +
> +static void
> +cmd_destroy_graph_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			 __rte_unused void *data)
> +{
> +	uint32_t lcore_id;
> +
> +	run_graph_walk = false;
> +	graph_walk_quit = true;
> +
> +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> +		rte_eal_wait_lcore(lcore_id);
> +
> +	destroy_graph();
> +	stop_eth_ports();
> +}
> +
> +cmdline_parse_inst_t cmd_destroy_graph = {
> +	.f = cmd_destroy_graph_parsed,
> +	.data = NULL,
> +	.help_str = "destroy_graph",
> +	.tokens = {
> +			(void *)&cmd_destroy_graph_destroy_graph,
> +			NULL,
> +		},
> +};
> +
> +/**** Start graph_walk ****/
> +struct cmd_start_graph_walk_result {
> +	cmdline_fixed_string_t start;
> +	cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> start, "start");
> +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =
> +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> graph_walk, "graph_walk");
> +
> +static void
> +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	static bool launch_graph_walk;
> +
> +	if (!launch_graph_walk) {
> +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> SKIP_MAIN);
> +		launch_graph_walk = true;
> +	}
> +
> +	run_graph_walk = true;
> +}
> +
> +cmdline_parse_inst_t cmd_start_graph_walk = {
> +	.f = cmd_start_graph_walk_parsed,
> +	.data = NULL,
> +	.help_str = "start graph_walk",
> +	.tokens = {
> +			(void *)&cmd_start_graph_walk_start,
> +			(void *)&cmd_start_graph_walk_graph_walk,
> +			NULL,
> +		},
> +};
> +
> +/**** Stop graph_walk ****/
> +struct cmd_stop_graph_walk_result {
> +	cmdline_fixed_string_t stop;
> +	cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> stop, "stop");
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =
> +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> graph_walk, "graph_walk");
> +
> +static void
> +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			   __rte_unused void *data)
> +{
> +	run_graph_walk = false;
> +}
> +
> +cmdline_parse_inst_t cmd_stop_graph_walk = {
> +	.f = cmd_stop_graph_walk_parsed,
> +	.data = NULL,
> +	.help_str = "stop graph_walk",
> +	.tokens = {
> +			(void *)&cmd_stop_graph_walk_stop,
> +			(void *)&cmd_stop_graph_walk_graph_walk,
> +			NULL,
> +		},
> +};
> +
> +/**** Show graph_stats ****/
> +struct cmd_show_graph_stats_result {
> +	cmdline_fixed_string_t show;
> +	cmdline_fixed_string_t graph_stats;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> show, "show");
> +static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> graph_stats, "graph_stats");
> +
> +static void
> +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	if (rte_graph_has_stats_feature()) {
> +		if (stats)
> +			rte_graph_cluster_stats_get(stats, 0);
> +	} else {
> +		printf(" graph stats feature not enabled in rte_config.\n");
> +	}
> +}
> +
> +cmdline_parse_inst_t cmd_show_graph_stats = {
> +	.f = cmd_show_graph_stats_parsed,
> +	.data = NULL,
> +	.help_str = "show graph_stats",
> +	.tokens = {
> +			(void *)&cmd_show_graph_stats_show,
> +			(void *)&cmd_show_graph_stats_graph_stats,
> +			NULL,
> +		},
> +};
> diff --git a/app/test-graph/cmdline_graph.h b/app/test-
> graph/cmdline_graph.h
> new file mode 100644
> index 0000000000..2846ff5425
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _CMDLINE_GRAPH_H_
> +#define _CMDLINE_GRAPH_H_
> +
> +extern cmdline_parse_inst_t cmd_show_node_list;
> +extern cmdline_parse_inst_t cmd_set_lcore_config;
> +
> +extern cmdline_parse_inst_t cmd_create_graph;
> +extern cmdline_parse_inst_t cmd_destroy_graph;
> +
> +extern cmdline_parse_inst_t cmd_start_graph_walk;
> +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> +
> +extern cmdline_parse_inst_t cmd_show_graph_stats;
> +
> +#endif /* _CMDLINE_GRAPH_H_ */
> diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> new file mode 100644
> index 0000000000..d93802a975
> --- /dev/null
> +++ b/app/test-graph/meson.build
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(C) 2023 Marvell International Ltd.
> +
> +# override default name to drop the hyphen
> +name = 'test-graph'
> +cflags += '-Wno-deprecated-declarations'
> +sources = files(
> +        'cmdline.c',
> +        'cmdline_graph.c',
> +        'parameters.c',
> +        'testgraph.c',
> +)
> +
> +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> new file mode 100644
> index 0000000000..b990ca4a1c
> --- /dev/null
> +++ b/app/test-graph/parameters.c
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +
> +#include "testgraph.h"
> +
> +static const char short_options[] = "p:" /* portmask */
> +				    "P"	 /* promiscuous */
> +				    "i"	 /* interactive */
> +	;
> +
> +#define CMD_LINE_OPT_CONFIG	   "config"
> +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> +#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
> +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> +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_CONFIG_NUM,
> +	CMD_LINE_OPT_NODE_PATTERN_NUM,
> +	CMD_LINE_OPT_INTERACTIVE_NUM,
> +	CMD_LINE_OPT_NO_NUMA_NUM,
> +	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> +};
> +
> +static const struct option lgopts[] = {
> +	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> +	{CMD_LINE_OPT_NODE_PATTERN, 1, 0,
> CMD_LINE_OPT_NODE_PATTERN_NUM},
> +	{CMD_LINE_OPT_INTERACTIVE, 0, 0,
> CMD_LINE_OPT_INTERACTIVE_NUM},
> +	{CMD_LINE_OPT_NO_NUMA, 0, 0,
> CMD_LINE_OPT_NO_NUMA_NUM},
> +	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0,
> CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> +	{NULL, 0, 0, 0},
> +};
> +
> +/* Display usage */
> +static void
> +print_usage(const char *prgname)
> +{
> +	fprintf(stderr,
> +		"%s [EAL options] --"
> +		" -p PORTMASK"
> +		" [-P]"
> +		" [-i]"
> +		" --config (port,queue,lcore)[,(port,queue,lcore)]"
> +		" --node-pattern
> (node_name0,node_name1[,node_nameX)]"
> +		" [--no-numa]"
> +		" [--per-port-pool]"
> +		" [--interactive]"
> +
> +		"  -p PORTMASK: Hexadecimal bitmask of ports to
> configure\n"
> +		"  -P : Enable promiscuous mode\n"
> +		"  -i : Enter interactive mode\n"
> +		"  --config (port,queue,lcore): Rx queue configuration\n"
> +		"  --node-pattern (node_names): node patterns to create
> graph\n"
> +		"  --no-numa: Disable numa awareness\n"
> +		"  --per-port-pool: Use separate buffer pool per port\n"
> +		"  --interactive: Enter interactive mode\n",
> +		prgname);
> +}
> +
> +static int
> +parse_portmask(const char *portmask)
> +{
> +	char *end = NULL;
> +	unsigned long pm;
> +
> +	/* Parse hexadecimal string */
> +	pm = strtoul(portmask, &end, 16);
> +	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> +		return 0;
> +
> +	return pm;
> +}
> +
> +/* Parse the argument given in the command line of the application */
> +int
> +parse_cmdline_args(int argc, char **argv)
> +{
> +	char *prgname = argv[0];
> +	int option_index;
> +	char **argvopt;
> +	int opt, ret;
> +
> +	argvopt = argv;
> +
> +	/* Error or normal output strings. */
> +	while ((opt = getopt_long(argc, argvopt, short_options, lgopts,
> &option_index)) != EOF) {
> +
> +		switch (opt) {
> +		/* Portmask */
> +		case 'p':
> +			enabled_port_mask = parse_portmask(optarg);
> +			if (enabled_port_mask == 0) {
> +				fprintf(stderr, "Invalid portmask\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +
> +		case 'P':
> +			promiscuous_on = 1;
> +			break;
> +
> +		/* Long options */
> +		case CMD_LINE_OPT_CONFIG_NUM:
> +			ret = parse_config(optarg);
> +			if (ret) {
> +				fprintf(stderr, "Invalid config\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +		case CMD_LINE_OPT_NODE_PATTERN_NUM:
> +			ret = parse_node_patterns(optarg);
> +			if (ret) {
> +				fprintf(stderr, "Invalid node_patterns\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +
> +		case CMD_LINE_OPT_INTERACTIVE_NUM:
> +		case 'i':
> +			printf("Interactive-mode selected\n");
> +			interactive = 1;
> +			break;
> +
> +		case CMD_LINE_OPT_NO_NUMA_NUM:
> +			numa_on = 0;
> +			break;
> +
> +		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> +			printf("Per port buffer pool is enabled\n");
> +			per_port_pool = 1;
> +			break;
> +
> +		default:
> +			print_usage(prgname);
> +			return -1;
> +		}
> +	}
> +
> +	if (optind >= 0)
> +		argv[optind - 1] = prgname;
> +	ret = optind - 1;
> +	optind = 1; /* Reset getopt lib */
> +
> +	return ret;
> +}
> diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> new file mode 100644
> index 0000000000..aff921acf2
> --- /dev/null
> +++ b/app/test-graph/testgraph.c
> @@ -0,0 +1,1426 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fnmatch.h>
> +#include <signal.h>
> +#include <stdlib.h>
> +
> +#include <rte_bus.h>
> +#include <rte_byteorder.h>
> +#include <rte_common.h>
> +#include <rte_dev.h>
> +#include <rte_eal.h>
> +#include <rte_ethdev.h>
> +#include <rte_ether.h>
> +#include <rte_graph_worker.h>
> +#include <rte_launch.h>
> +#include <rte_lcore.h>
> +#include <rte_log.h>
> +#include <rte_malloc.h>
> +#include <rte_mempool.h>
> +#include <rte_per_lcore.h>
> +#include <ethdev_rx_priv.h>
> +#include <ethdev_tx_priv.h>
> +#include <punt_kernel_priv.h>
> +#include <kernel_recv_priv.h>
> +
> +#include "testgraph.h"
> +
> +/* Log type */
> +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> +
> +/*
> + * Configurable number of RX/TX ring descriptors
> + */
> +#define RX_DESC_DEFAULT 1024
> +#define TX_DESC_DEFAULT 1024
> +
> +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> +#define MAX_RX_QUEUE_PER_PORT 128
> +
> +#define MAX_RX_QUEUE_PER_LCORE 16
> +
> +#define NB_SOCKETS 8
> +
> +/* Static global variables used within this file. */
> +uint16_t nb_rxd = RX_DESC_DEFAULT;
> +uint16_t nb_txd = TX_DESC_DEFAULT;
> +
> +static volatile bool force_quit;
> +volatile bool graph_walk_quit;
> +volatile bool run_graph_walk = true;
> +
> +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};
> +const char **node_patterns;
> +uint8_t num_patterns;
> +
> +uint8_t interactive; /**< interactive mode is off by default */
> +int promiscuous_on; /**< Ports set in promiscuous mode off by default. */
> +int numa_on = 1;   /**< NUMA is enabled by default. */
> +int per_port_pool; /**< Use separate buffer pools per port, disabled by
> default */
> +int testgraph_logtype; /**< Log type for testgraph logs */
> +
> +struct rte_graph_cluster_stats *stats;
> +
> +uint32_t enabled_port_mask; /**< Mask of enabled ports */
> +
> +uint32_t nb_conf;
> +
> +struct lcore_rx_queue {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +/* Lcore conf */
> +struct lcore_conf {
> +	uint16_t n_rx_queue;
> +	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> +	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> +	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
> +
> +	struct rte_graph *graph;
> +	char name[RTE_GRAPH_NAMESIZE];
> +	rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +
> +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> +static struct lcore_params lcore_params_array_default[] = {
> +	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> +	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> +};
> +
> +struct lcore_params *lcore_params = lcore_params_array_default;
> +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +static struct rte_eth_conf port_conf = {
> +	.rxmode = {
> +			.mq_mode = RTE_ETH_MQ_RX_RSS,
> +		},
> +	.rx_adv_conf = {
> +			.rss_conf = {
> +					.rss_key = NULL,
> +					.rss_hf = RTE_ETH_RSS_IP,
> +				},
> +		},
> +	.txmode = {
> +			.mq_mode = RTE_ETH_MQ_TX_NONE,
> +		},
> +};
> +
> +static const struct node_list test_node_list[] = {{{"ethdev_rx", "ethdev_tx"},
> 0, 2},
> +						{{"ethdev_rx",
> "punt_kernel"}, 0, 2},
> +						{{"kernel_recv", "ethdev_tx"},
> 0, 2} };
> +
> +static const struct node_list supported_nodes[] = {{{"ethdev_rx"},
> TEST_GRAPH_ETHDEV_RX_NODE, 1},
> +						{{"ethdev_tx"},
> TEST_GRAPH_ETHDEV_TX_NODE, 1},
> +						{{"punt_kernel"},
> TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> +						{{"kernel_recv"},
> TEST_GRAPH_KERNEL_RECV_NODE, 1},
> +						{{"ip4_lookup"},
> TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> +						{{"ip4_rewrite"},
> TEST_GRAPH_IP4_REWRITE_NODE, 1},
> +						{{"pkt_cls"},
> TEST_GRAPH_PKT_CLS_NODE, 1},
> +						{{"pkt_drop"},
> TEST_GRAPH_PKT_DROP_NODE, 1},
> +						{{"NULL"},
> TEST_GRAPH_NULL_NODE, 1} };
> +
> +static struct rte_mempool
> *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> +
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +
> +static int
> +check_lcore_params(void)
> +{
> +	uint8_t queue, lcore;
> +	int socketid;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		queue = lcore_params[i].queue_id;
> +		if (queue >= MAX_RX_QUEUE_PER_PORT) {
> +			printf("Invalid queue number: %hhu\n", queue);
> +			return -1;
> +		}
> +		lcore = lcore_params[i].lcore_id;
> +		if (!rte_lcore_is_enabled(lcore)) {
> +			printf("Error: lcore %hhu is not enabled in lcore
> mask\n", lcore);
> +			return -1;
> +		}
> +
> +		if (lcore == rte_get_main_lcore()) {
> +			printf("Error: lcore %u is main lcore\n", lcore);
> +			return -1;
> +		}
> +		socketid = rte_lcore_to_socket_id(lcore);
> +		if ((socketid != 0) && (numa_on == 0)) {
> +			printf("Warning: lcore %hhu is on socket %d with
> numa off\n", lcore,
> +			       socketid);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +check_port_config(void)
> +{
> +	uint16_t portid;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		portid = lcore_params[i].port_id;
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("Port %u is not enabled in port mask\n",
> portid);
> +			return -1;
> +		}
> +		if (!rte_eth_dev_is_valid_port(portid)) {
> +			printf("Port %u is not present on the board\n",
> portid);
> +			return -1;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static uint8_t
> +get_port_n_rx_queues(const uint16_t port)
> +{
> +	int queue = -1;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		if (lcore_params[i].port_id == port) {
> +			if (lcore_params[i].queue_id == queue + 1)
> +				queue = lcore_params[i].queue_id;
> +			else
> +				rte_exit(EXIT_FAILURE, "Queue ids of the port
> %d must be"
> +					 " in sequence and must start with
> 0\n",
> +					 lcore_params[i].port_id);
> +		}
> +	}
> +
> +	return (uint8_t)(++queue);
> +}
> +
> +static int
> +init_lcore_rx_queues(void)
> +{
> +	uint16_t i, nb_rx_queue;
> +	uint8_t lcore;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		lcore = lcore_params[i].lcore_id;
> +		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> +		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> +			printf("Error: too many queues (%u) for lcore: %u\n",
> +			       (unsigned int)nb_rx_queue + 1, (unsigned
> int)lcore);
> +			return -1;
> +		}
> +
> +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id =
> lcore_params[i].port_id;
> +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id =
> lcore_params[i].queue_id;
> +		lcore_conf[lcore].n_rx_queue++;
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +validate_config(void)
> +{
> +	int rc = -1;
> +
> +	if (check_lcore_params() < 0) {
> +		printf("check_lcore_params() failed\n");
> +		goto exit;
> +	}
> +
> +	if (init_lcore_rx_queues() < 0) {
> +		printf("init_lcore_rx_queues() failed\n");
> +		goto exit;
> +	}
> +
> +	if (check_port_config() < 0) {
> +		printf("check_port_config() failed\n");
> +		goto exit;
> +	}
> +
> +	return 0;
> +
> +exit:
> +	return rc;
> +}
> +
> +#define MEMPOOL_CACHE_SIZE 256
> +
> +/*
> + * This expression is used to calculate the number of mbufs needed
> + * depending on user input, taking  into account memory for rx and
> + * tx hardware rings, cache per lcore and mtable per port per lcore.
> + * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum
> + * value of 8192
> + */
> +#define NB_MBUF(nports)                                                                            \
> +	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores *
> RTE_GRAPH_BURST_SIZE +       \
> +		 nports * nb_tx_queue * nb_txd + nb_lcores *
> MEMPOOL_CACHE_SIZE),                  \
> +		8192u)
> +
> +static int
> +init_mem(uint16_t portid, uint32_t nb_mbuf)
> +{
> +	uint32_t lcore_id;
> +	int socketid;
> +	char s[64];
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		if (numa_on)
> +			socketid = rte_lcore_to_socket_id(lcore_id);
> +		else
> +			socketid = 0;
> +
> +		if (socketid >= NB_SOCKETS) {
> +			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out
> of range %d\n",
> +				 socketid, lcore_id, NB_SOCKETS);
> +		}
> +
> +		if (pktmbuf_pool[portid][socketid] == NULL) {
> +			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid,
> socketid);
> +			/* Create a pool with priv size of a cacheline */
> +			pktmbuf_pool[portid][socketid] =
> rte_pktmbuf_pool_create(
> +				s, nb_mbuf, MEMPOOL_CACHE_SIZE,
> RTE_CACHE_LINE_SIZE,
> +				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> +			if (pktmbuf_pool[portid][socketid] == NULL)
> +				rte_exit(EXIT_FAILURE, "Cannot init mbuf
> pool on socket %d\n",
> +					 socketid);
> +			else
> +				printf("Allocated mbuf pool on socket %d\n",
> socketid);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +	uint8_t count, all_ports_up, print_flag = 0;
> +	struct rte_eth_link link;
> +	uint16_t portid;
> +	int ret;
> +	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> +
> +	printf("\nChecking link status");
> +	fflush(stdout);
> +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +		if (force_quit)
> +			return;
> +		all_ports_up = 1;
> +		RTE_ETH_FOREACH_DEV(portid) {
> +			if (force_quit)
> +				return;
> +			if ((port_mask & (1 << portid)) == 0)
> +				continue;
> +			memset(&link, 0, sizeof(link));
> +			ret = rte_eth_link_get_nowait(portid, &link);
> +			if (ret < 0) {
> +				all_ports_up = 0;
> +				if (print_flag == 1)
> +					printf("Port %u link get failed: %s\n",
> portid,
> +					       rte_strerror(-ret));
> +				continue;
> +			}
> +			/* Print link status if flag set */
> +			if (print_flag == 1) {
> +				rte_eth_link_to_str(link_status_text,
> sizeof(link_status_text),
> +						    &link);
> +				printf("Port %d %s\n", portid,
> link_status_text);
> +				continue;
> +			}
> +			/* Clear all_ports_up flag if any link down */
> +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> +				all_ports_up = 0;
> +				break;
> +			}
> +		}
> +		/* After finally printing all link status, get out */
> +		if (print_flag == 1)
> +			break;
> +
> +		if (all_ports_up == 0) {
> +			printf(".");
> +			fflush(stdout);
> +			rte_delay_ms(CHECK_INTERVAL);
> +		}
> +
> +		/* Set the print_flag if all ports up or timeout */
> +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +			print_flag = 1;
> +			printf("Done\n");
> +		}
> +	}
> +}
> +
> +static void
> +signal_handler(int signum)
> +{
> +	if (signum == SIGINT || signum == SIGTERM) {
> +		printf("\n\nSignal %d received, preparing to exit...\n",
> signum);
> +		force_quit = true;
> +	}
> +	prompt_exit();
> +}
> +
> +int
> +graph_main_loop(void *conf)
> +{
> +	struct lcore_conf *qconf;
> +	struct rte_graph *graph;
> +	uint32_t lcore_id;
> +
> +	RTE_SET_USED(conf);
> +
> +	lcore_id = rte_lcore_id();
> +	qconf = &lcore_conf[lcore_id];
> +	graph = qconf->graph;
> +
> +	if (!graph) {
> +		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to
> do\n", lcore_id);
> +		return 0;
> +	}
> +
> +	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u, graph
> %s(%p)\n", lcore_id,
> +		qconf->name, graph);
> +
> +	while (likely(!force_quit && !graph_walk_quit)) {
> +		if (likely(run_graph_walk))
> +			rte_graph_walk(graph);
> +	}
> +
> +	return 0;
> +}
> +
> +struct rte_graph_cluster_stats *
> +create_graph_cluster_stats(void)
> +{
> +	struct rte_graph_cluster_stats_param s_param;
> +	struct rte_graph_cluster_stats *stat;
> +	const char *pattern = "worker_*";
> +
> +	/* 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;
> +
> +	stat = rte_graph_cluster_stats_create(&s_param);
> +	if (stat == NULL)
> +		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> +
> +	return stat;
> +}
> +
> +static void
> +print_stats(void)
> +{
> +	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> +	const char clr[] = {27, '[', '2', 'J', '\0'};
> +
> +	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);
> +}
> +
> +int
> +parse_config(const char *q_arg)
> +{
> +	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE,
> _NUM_FLD };
> +	unsigned long int_fld[_NUM_FLD];
> +	const char *p, *p0 = q_arg;
> +	char *str_fld[_NUM_FLD];
> +	uint32_t size;
> +	char s[256];
> +	char *end;
> +	int i;
> +
> +	nb_lcore_params = 0;
> +
> +	while ((p = strchr(p0, '(')) != NULL) {
> +		++p;
> +		p0 = strchr(p, ')');
> +		if (p0 == NULL)
> +			goto exit;
> +
> +		size = p0 - p;
> +		if (size >= sizeof(s))
> +			goto exit;
> +
> +		memcpy(s, p, size);
> +		s[size] = '\0';
> +		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') !=
> _NUM_FLD)
> +			goto exit;
> +		for (i = 0; i < _NUM_FLD; i++) {
> +			errno = 0;
> +			int_fld[i] = strtoul(str_fld[i], &end, 0);
> +			if (errno != 0 || end == str_fld[i])
> +				goto exit;
> +		}
> +
> +		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> +			printf("Exceeded max number of lcore params:
> %hu\n", nb_lcore_params);
> +			goto exit;
> +		}
> +
> +		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS ||
> int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> +			printf("Invalid port/lcore id\n");
> +			goto exit;
> +		}
> +
> +		lcore_params_array[nb_lcore_params].port_id =
> (uint8_t)int_fld[FLD_PORT];
> +		lcore_params_array[nb_lcore_params].queue_id =
> (uint8_t)int_fld[FLD_QUEUE];
> +		lcore_params_array[nb_lcore_params].lcore_id =
> (uint8_t)int_fld[FLD_LCORE];
> +		++nb_lcore_params;
> +	}
> +	lcore_params = lcore_params_array;
> +
> +	return 0;
> +exit:
> +	/* Revert to default config */
> +	lcore_params = lcore_params_array_default;
> +	nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +	return -1;
> +}
> +
> +int
> +parse_node_patterns(const char *q_arg)
> +{
> +	const char *p, *p0 = q_arg;
> +	int ret = -EINVAL;
> +	uint32_t size;
> +
> +	num_patterns = 0;
> +
> +	p = strchr(p0, '(');
> +	if (p != NULL) {
> +		++p;
> +		while ((p0 = strchr(p, ',')) != NULL) {
> +			size = p0 - p;
> +			if (size >= RTE_NODE_NAMESIZE)
> +				goto exit;
> +
> +			if (num_patterns >= MAX_NODE_PATTERNS) {
> +				printf("Too many nodes passed.\n");
> +				goto exit;
> +			}
> +
> +			memcpy(node_pattern[num_patterns++], p, size);
> +			p = p0 + 1;
> +		}
> +
> +		p0 = strchr(p, ')');
> +		if (p0 != NULL) {
> +			size = p0 - p;
> +			if (size >= RTE_NODE_NAMESIZE)
> +				goto exit;
> +
> +			if (num_patterns >= MAX_NODE_PATTERNS) {
> +				printf("Too many nodes passed.\n");
> +				goto exit;
> +			}
> +
> +			memcpy(node_pattern[num_patterns++], p, size);
> +		} else {
> +			goto exit;
> +		}
> +	} else {
> +		goto exit;
> +	}
> +
> +	return 0;
> +exit:
> +	return ret;
> +}
> +
> +static void
> +set_default_node_pattern(void)
> +{
> +	uint16_t idx;
> +
> +	for (idx = 0; idx < test_node_list[0].size; idx++)
> +		strcpy(node_pattern[num_patterns++],
> test_node_list[0].nodes[idx]);
> +}
> +
> +int
> +validate_node_names(uint64_t *valid_nodes)
> +{
> +	rte_node_t node_cnt = rte_node_max_count();
> +	bool pattern_matched = false;
> +	rte_node_t id = 0;
> +	int ret = -EINVAL;
> +	uint16_t idx, i, j;
> +
> +	for (idx = 0; idx < num_patterns; idx++) {
> +		for (id = 0; id < node_cnt; id++) {
> +			if (strncmp(node_pattern[idx],
> rte_node_id_to_name(id),
> +				    RTE_GRAPH_NAMESIZE) == 0)
> +				break;
> +		}
> +		if (node_cnt == id) {
> +			printf("Invalid node name passed\n");
> +			return ret;
> +		}
> +	}
> +
> +	for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +		idx = 0;
> +		if (test_node_list[i].size == num_patterns) {
> +			for (j = 0; j < num_patterns; j++) {
> +				if (strncmp(node_pattern[j],
> test_node_list[i].nodes[j],
> +				    RTE_GRAPH_NAMESIZE) == 0)
> +					idx++;
> +			}
> +			if (idx == num_patterns)
> +				pattern_matched = true;
> +		}
> +	}
> +
> +	if (!pattern_matched) {
> +		printf("Unsupported node pattern passed\n\n");
> +		printf("Test supported node patterns are:\n");
> +		for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +			printf("(");
> +			for (j = 0; j < (test_node_list[i].size - 1); j++)
> +				printf("%s,", test_node_list[i].nodes[j]);
> +			printf("%s", test_node_list[i].nodes[j]);
> +			printf(")\n");
> +		}
> +
> +		return ret;
> +	}
> +
> +	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> +		for (j = 0; j < num_patterns; j++) {
> +			if (strncmp(node_pattern[j],
> supported_nodes[i].nodes[0],
> +				    RTE_GRAPH_NAMESIZE) == 0) {
> +				*valid_nodes |= supported_nodes[i].test_id;
> +				break;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void
> +cleanup_node_pattern(void)
> +{
> +	while (num_patterns) {
> +		memset(node_pattern[num_patterns - 1], 0,
> RTE_GRAPH_NAMESIZE);
> +		num_patterns--;
> +	}
> +}
> +
> +static int
> +ethdev_tx_node_configure(void)
> +{
> +	struct ethdev_tx_node_main *tx_node_data;
> +	struct rte_node_register *tx_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	uint16_t port_id;
> +	uint32_t id;
> +
> +	tx_node_data = ethdev_tx_node_data_get();
> +	tx_node = ethdev_tx_node_get();
> +
> +	RTE_ETH_FOREACH_DEV(port_id) {
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << port_id)) == 0) {
> +			printf("\nSkipping disabled port %d\n", port_id);
> +			continue;
> +		}
> +
> +		if (!rte_eth_dev_is_valid_port(port_id))
> +			return -EINVAL;
> +
> +		/* Create a per port tx node from base node */
> +		snprintf(name, sizeof(name), "%u", port_id);
> +		id = rte_node_clone(tx_node->id, name);
> +		tx_node_data->nodes[port_id] = id;
> +
> +		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node->name,
> name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +punt_kernel_node_configure(void)
> +{
> +	struct rte_node_register *punt_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	uint32_t id;
> +
> +	punt_node = punt_kernel_node_get();
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Create a per lcore punt_kernel node from base node */
> +		snprintf(name, sizeof(name), "%u", lcore_id);
> +		id = rte_node_clone(punt_node->id, name);
> +		strcpy(qconf->punt_kernel_node_name,
> rte_node_id_to_name(id));
> +
> +		printf("punt_kernel node %s-%s: is at %u\n", punt_node-
> >name, name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +kernel_recv_node_configure(void)
> +{
> +	struct rte_node_register *recv_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	uint32_t id;
> +
> +	recv_node = kernel_recv_node_get();
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Create a per lcore kernel_recv node from base node */
> +		snprintf(name, sizeof(name), "%u", lcore_id);
> +		id = rte_node_clone(recv_node->id, name);
> +		strcpy(qconf->kernel_recv_node_name,
> rte_node_id_to_name(id));
> +
> +		printf("kernel_recv node %s-%s: is at %u\n", recv_node-
> >name, name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t
> nb_confs)
> +{
> +	char name[RTE_NODE_NAMESIZE];
> +	uint16_t i, j, port_id;
> +	uint32_t id;
> +
> +	for (i = 0; i < nb_confs; i++) {
> +		port_id = conf[i].port_id;
> +
> +		if (!rte_eth_dev_is_valid_port(port_id))
> +			return -EINVAL;
> +
> +		/* Create node for each rx port queue pair */
> +		for (j = 0; j < conf[i].num_rx_queues; j++) {
> +			struct ethdev_rx_node_main *rx_node_data;
> +			struct rte_node_register *rx_node;
> +			ethdev_rx_node_elem_t *elem;
> +
> +			rx_node_data = ethdev_rx_get_node_data_get();
> +			rx_node = ethdev_rx_node_get();
> +			snprintf(name, sizeof(name), "%u-%u", port_id, j);
> +			/* Clone a new rx node with same edges as parent */
> +			id = rte_node_clone(rx_node->id, name);
> +			if (id == RTE_NODE_ID_INVALID)
> +				return -EIO;
> +
> +			/* Add it to list of ethdev rx nodes for lookup */
> +			elem = malloc(sizeof(ethdev_rx_node_elem_t));
> +			if (elem == NULL)
> +				return -ENOMEM;
> +			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> +			elem->ctx.port_id = port_id;
> +			elem->ctx.queue_id = j;
> +			elem->nid = id;
> +			elem->next = rx_node_data->head;
> +			rx_node_data->head = elem;
> +
> +			printf("ethdev:: Rx node %s-%s: is at %u\n", rx_node-
> >name, name, id);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> +{
> +	struct ethdev_rx_node_main *rx_node_data;
> +	ethdev_rx_node_elem_t *elem;
> +	char *next_nodes[16];
> +	rte_edge_t count;
> +	uint16_t i;
> +
> +	count = rte_node_edge_count(id);
> +	rte_node_edge_get(id, next_nodes);
> +
> +	for (i = 0; i < count; i++) {
> +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> +			rx_node_data = ethdev_rx_get_node_data_get();
> +			elem = rx_node_data->head;
> +			while (elem->next != rx_node_data->head) {
> +				if (elem->nid == id)
> +					break;
> +				elem = elem->next;
> +			}
> +
> +			if (elem->nid == id)
> +				elem->ctx.cls_next = i;
> +			break;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_tx_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"ethdev_tx-*"};
> +	uint16_t queue, queue_id, port_id;
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	uint32_t rx_id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +		port_id = qconf->rx_queue_list[queue].port_id;
> +		queue_id = qconf->rx_queue_list[queue].queue_id;
> +
> +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id,
> queue_id);
> +		rx_id = rte_node_from_name(name);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		/* Prepare the actual name of the cloned node */
> +		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> +
> +		/* Update ethdev_rx node edges */
> +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> &next_node, 1);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		/* Update node_next details */
> +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"punt_kernel-*"};
> +	uint16_t queueid, portid, queue;
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	rte_node_t rx_id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +
> +		portid = qconf->rx_queue_list[queue].port_id;
> +		queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid,
> queueid);
> +		rx_id = rte_node_from_name(name);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		next_node = qconf->punt_kernel_node_name;
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], next_node);
> +
> +		/* Update ethdev_rx node edges */
> +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> &next_node, 1);
> +
> +		/* Update node_next details */
> +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
> +{
> +	struct kernel_recv_node_main *rx_node_data;
> +	kernel_recv_node_elem_t *elem;
> +	char *next_nodes[16];
> +	rte_edge_t count;
> +	uint16_t i;
> +
> +	count = rte_node_edge_count(id);
> +	rte_node_edge_get(id, next_nodes);
> +
> +	for (i = 0; i < count; i++) {
> +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> +			rx_node_data = kernel_recv_node_data_get();
> +			elem = rx_node_data->head;
> +			while (elem->next != rx_node_data->head) {
> +				if (elem->nid == id)
> +					break;
> +				elem = elem->next;
> +			}
> +
> +			if (elem->nid == id)
> +				elem->ctx.recv_info->cls_next = i;
> +			break;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"ethdev_tx-*"};
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	uint16_t port_id;
> +	rte_node_t id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	id = rte_node_from_name(qconf->kernel_recv_node_name);
> +
> +	/* Fill node pattern */
> +	strcpy(node_pattern[num_patterns++], qconf-
> >kernel_recv_node_name);
> +
> +	port_id = lcore_id;
> +
> +	if ((enabled_port_mask & (1 << port_id)) == 0) {
> +		/* Use available port_id */
> +		RTE_ETH_FOREACH_DEV(port_id) {
> +			if ((enabled_port_mask & (1 << port_id)) != 0)
> +				break;
> +		}
> +	}
> +
> +	if (!rte_eth_dev_is_valid_port(port_id)) {
> +		printf("Port %u is not present on the board\n", port_id);
> +		return -1;
> +	}
> +
> +	/* Prepare the actual name of the cloned node */
> +	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> +
> +	/* Update ethdev_rx node edges */
> +	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node, 1);
> +
> +	/* Fill node pattern */
> +	strcpy(node_pattern[num_patterns++], name);
> +
> +	/* Update node_next details */
> +	update_kernel_recv_node_next(id, pattern[0]);
> +
> +	return 0;
> +}
> +
> +uint32_t
> +ethdev_ports_setup(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	uint32_t nb_tx_queue, nb_lcores;
> +	uint32_t nb_ports, nb_conf = 0;
> +	uint8_t nb_rx_queue;
> +	uint16_t portid;
> +	int ret;
> +
> +	nb_ports = rte_eth_dev_count_avail();
> +	nb_lcores = rte_lcore_count();
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		struct rte_eth_conf local_port_conf = port_conf;
> +
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("\nSkipping disabled port %d\n", portid);
> +			continue;
> +		}
> +
> +		/* Init port */
> +		printf("Initializing port %d ... ", portid);
> +		fflush(stdout);
> +
> +		nb_rx_queue = get_port_n_rx_queues(portid);
> +		nb_tx_queue = nb_lcores;
> +		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> +			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> +		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n",
> nb_rx_queue, nb_tx_queue);
> +
> +		rte_eth_dev_info_get(portid, &dev_info);
> +
> +		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 !=
> +		    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, 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_rx_queue,
> nb_tx_queue, &local_port_conf);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "Cannot configure device:
> err=%d, port=%d\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=%d\n",
> +				 ret, portid);
> +
> +		/* Init memory */
> +		if (!per_port_pool)
> +			ret = init_mem(0, NB_MBUF(nb_ports));
> +		else
> +			ret = init_mem(portid, NB_MBUF(1));
> +
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> +
> +		/* Setup ethdev node config */
> +		ethdev_conf[nb_conf].port_id = portid;
> +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> +		if (!per_port_pool)
> +			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> +
> +		else
> +			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> +		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> +
> +		nb_conf++;
> +	}
> +
> +	return nb_conf;
> +}
> +
> +void
> +ethdev_rxq_configure(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	uint16_t queueid, portid;
> +	struct lcore_conf *qconf;
> +	uint8_t queue, socketid;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +		qconf = &lcore_conf[lcore_id];
> +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +		fflush(stdout);
> +
> +		/* Init RX queues */
> +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +			struct rte_eth_rxconf rxq_conf;
> +
> +			portid = qconf->rx_queue_list[queue].port_id;
> +			queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +			if (numa_on)
> +				socketid =
> (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +			else
> +				socketid = 0;
> +
> +			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> +			fflush(stdout);
> +
> +			rte_eth_dev_info_get(portid, &dev_info);
> +			rxq_conf = dev_info.default_rxconf;
> +			rxq_conf.offloads = port_conf.rxmode.offloads;
> +			if (!per_port_pool)
> +				ret = rte_eth_rx_queue_setup(portid,
> queueid, nb_rxd, socketid,
> +							     &rxq_conf,
> pktmbuf_pool[0][socketid]);
> +			else
> +				ret = rte_eth_rx_queue_setup(portid,
> queueid, nb_rxd, socketid,
> +							     &rxq_conf,
> +
> pktmbuf_pool[portid][socketid]);
> +			if (ret < 0)
> +				rte_exit(EXIT_FAILURE,
> "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> +					 ret, portid);
> +
> +			snprintf(qconf->rx_queue_list[queue].node_name,
> RTE_NODE_NAMESIZE,
> +				 "ethdev_rx-%u-%u", portid, queueid);
> +		}
> +	}
> +	printf("\n");
> +}
> +
> +void
> +ethdev_txq_configure(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	struct rte_eth_txconf *txconf;
> +	uint16_t queueid, portid;
> +	uint32_t lcore_id;
> +	uint8_t socketid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		struct rte_eth_conf local_port_conf = port_conf;
> +
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("\nSkipping disabled port %d\n", portid);
> +			continue;
> +		}
> +
> +		rte_eth_dev_info_get(portid, &dev_info);
> +
> +		/* Init one TX queue per (lcore,port) pair */
> +		queueid = 0;
> +		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +			if (rte_lcore_is_enabled(lcore_id) == 0)
> +				continue;
> +
> +			if (numa_on)
> +				socketid =
> (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +			else
> +				socketid = 0;
> +
> +			printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
> +			fflush(stdout);
> +
> +			txconf = &dev_info.default_txconf;
> +			txconf->offloads = local_port_conf.txmode.offloads;
> +			ret = rte_eth_tx_queue_setup(portid, queueid,
> nb_txd, socketid, txconf);
> +			if (ret < 0)
> +				rte_exit(EXIT_FAILURE,
> "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> +					 ret, portid);
> +			queueid++;
> +		}
> +	}
> +	printf("\n");
> +}
> +
> +int
> +configure_graph_nodes(uint64_t valid_nodes)
> +{
> +	int ret = 0;
> +
> +	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> +		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> +		if (ret) {
> +			printf("ethdev_rx_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> +		ret = ethdev_tx_node_configure();
> +		if (ret) {
> +			printf("ethdev_tx_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> +		ret = punt_kernel_node_configure();
> +		if (ret) {
> +			printf("punt_kernel_node_configure: err=%d\n",
> ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
> +		ret = kernel_recv_node_configure();
> +		if (ret) {
> +			printf("kernel_recv_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static int
> +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> +{
> +	int ret = 0;
> +
> +	num_patterns = 0;
> +
> +	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> TEST_GRAPH_ETHDEV_RX_NODE)) {
> +		ret = link_ethdev_rx_to_tx_node(lcore_id);
> +		if (ret) {
> +			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> +			goto exit;
> +		}
> +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE |
> TEST_GRAPH_PUNT_KERNEL_NODE)) {
> +		link_ethdev_rx_to_punt_kernel_node(lcore_id);
> +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> TEST_GRAPH_KERNEL_RECV_NODE)) {
> +		link_kernel_recv_to_ethdev_tx_node(lcore_id);
> +	} else {
> +		printf("Invalid node map\n");
> +		ret = -EINVAL;
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +void
> +start_eth_ports(void)
> +{
> +	uint16_t portid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +
> +		ret = rte_eth_dev_start(portid);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d,
> port=%d\n", ret, portid);
> +
> +		if (promiscuous_on)
> +			rte_eth_promiscuous_enable(portid);
> +	}
> +}
> +
> +void
> +stop_eth_ports(void)
> +{
> +	uint16_t portid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +		printf("Closing port %d...", portid);
> +		ret = rte_eth_dev_stop(portid);
> +		if (ret != 0)
> +			printf("Failed to stop port %u: %s\n", portid,
> rte_strerror(-ret));
> +		rte_eth_dev_close(portid);
> +	}
> +}
> +
> +int
> +create_graph(uint64_t valid_nodes)
> +{
> +	struct rte_graph_param graph_conf;
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	node_patterns = malloc(MAX_NODE_PATTERNS *
> sizeof(*node_patterns));
> +	if (!node_patterns)
> +		return -ENOMEM;
> +
> +	memset(&graph_conf, 0, sizeof(graph_conf));
> +	graph_conf.node_patterns = node_patterns;
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		rte_graph_t graph_id;
> +		rte_edge_t i;
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Skip graph creation if no source exists */
> +		if (!qconf->n_rx_queue)
> +			continue;
> +
> +		ret = link_graph_nodes(valid_nodes, lcore_id);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "link_graph_nodes():
> failed\n");
> +
> +		for (i = 0; i < num_patterns; i++) {
> +			graph_conf.node_patterns[i] = node_pattern[i];
> +			printf("%s,", graph_conf.node_patterns[i]);
> +		}
> +		printf("\n");
> +
> +		graph_conf.nb_node_patterns = num_patterns;
> +
> +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> lcore_id);
> +
> +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> +		if (graph_id == RTE_GRAPH_ID_INVALID)
> +			rte_exit(EXIT_FAILURE, "rte_graph_create(): graph_id
> invalid for lcore%u\n",
> +				 lcore_id);
> +
> +		qconf->graph_id = graph_id;
> +		qconf->graph = rte_graph_lookup(qconf->name);
> +
> +		if (!qconf->graph)
> +			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph
> %s not found\n",
> +				 qconf->name);
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +destroy_graph(void)
> +{
> +	uint32_t lcore_id;
> +	rte_graph_t id;
> +	int ret;
> +
> +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> +	{
> +		if (lcore_conf[lcore_id].graph) {
> +			id =
> rte_graph_from_name(lcore_conf[lcore_id].name);
> +			if (rte_graph_destroy(id)) {
> +				printf("graph_id %u destroy failed.\n", id);
> +				ret = -1;
> +			}
> +		}
> +	}
> +
> +	if (node_patterns)
> +		free(node_patterns);
> +
> +	return ret;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +	uint64_t valid_nodes;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	graph_walk_quit = false;
> +	force_quit = false;
> +	interactive = 0;
> +
> +	node_patterns = NULL;
> +
> +	signal(SIGINT, signal_handler);
> +	signal(SIGTERM, signal_handler);
> +
> +	testgraph_logtype = rte_log_register("testgraph");
> +	if (testgraph_logtype < 0)
> +		rte_exit(EXIT_FAILURE, "Cannot register log type");
> +
> +	set_default_node_pattern();
> +
> +	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> +
> +	ret = rte_eal_init(argc, argv);
> +	if (ret < 0)
> +		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n",
> rte_strerror(rte_errno));
> +	argc -= ret;
> +	argv += ret;
> +
> +	if (argc > 1) {
> +		ret = parse_cmdline_args(argc, argv);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "Invalid command line
> parameters\n");
> +	}
> +
> +#ifdef RTE_LIB_CMDLINE
> +	if (init_cmdline() != 0)
> +		rte_exit(EXIT_FAILURE, "Could not initialise cmdline
> context.\n");
> +
> +	if (interactive == 1) {
> +		prompt();
> +	} else
> +#endif
> +	{
> +		if (validate_config() < 0)
> +			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> +
> +		ret = validate_node_names(&valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "validate_node_names:
> err=%d\n", ret);
> +
> +		nb_conf = ethdev_ports_setup();
> +
> +		ethdev_rxq_configure();
> +
> +		ethdev_txq_configure();
> +
> +		ret = configure_graph_nodes(valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> err=%d\n", ret);
> +
> +		ret = create_graph(valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n",
> ret);
> +
> +		stats = create_graph_cluster_stats();
> +		if (stats == NULL)
> +			rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> failed\n");
> +
> +		check_all_ports_link_status(enabled_port_mask);
> +
> +		start_eth_ports();
> +
> +		/* Launch per-lcore init on every worker lcore */
> +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> SKIP_MAIN);
> +
> +		/* Accumulate and print stats on main until exit */
> +		if (rte_graph_has_stats_feature())
> +			print_stats();
> +
> +		/* Wait for worker cores to exit */
> +		RTE_LCORE_FOREACH_WORKER(lcore_id) {
> +			ret = rte_eal_wait_lcore(lcore_id);
> +			if (ret < 0)
> +				break;
> +		}
> +
> +		ret = destroy_graph();
> +
> +		stop_eth_ports();
> +	}
> +
> +	/* clean up the EAL */
> +	ret = rte_eal_cleanup();
> +	if (ret != 0)
> +		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-
> ret));
> +
> +	return EXIT_SUCCESS;
> +}
> diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> new file mode 100644
> index 0000000000..ea1f8ef4fa
> --- /dev/null
> +++ b/app/test-graph/testgraph.h
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _TESTGRAPH_H_
> +#define _TESTGRAPH_H_
> +
> +#include <stdbool.h>
> +
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +
> +
> +#define MAX_LCORE_PARAMS 1024
> +#define MAX_NODE_PATTERNS 128
> +
> +#ifndef BIT_ULL
> +#define BIT_ULL(nr) (1ULL << (nr))
> +#endif
> +
> +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> +
> +static volatile bool force_quit;
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern uint32_t enabled_port_mask;
> +extern uint32_t nb_conf;
> +
> +extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by
> default. */
> +extern uint8_t interactive;	   /**< interactive mode is disabled by
> default. */
> +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> +extern int numa_on;		   /**< NUMA is enabled by default. */
> +extern int per_port_pool;
> +
> +extern volatile bool graph_walk_quit;
> +extern volatile bool run_graph_walk;
> +extern struct rte_graph_cluster_stats *stats;
> +
> +extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> +extern uint8_t num_patterns;
> +
> +struct node_list {
> +	const char *nodes[MAX_NODE_PATTERNS];
> +	uint64_t test_id;
> +	uint8_t size;
> +};
> +
> +struct lcore_params {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +void prompt(void);
> +void prompt_exit(void);
> +int init_cmdline(void);
> +int validate_config(void);
> +int parse_cmdline_args(int argc, char **argv);
> +uint32_t ethdev_ports_setup(void);
> +void ethdev_rxq_configure(void);
> +void ethdev_txq_configure(void);
> +void start_eth_ports(void);
> +void stop_eth_ports(void);
> +int create_graph(uint64_t valid_nodes);
> +int destroy_graph(void);
> +int graph_main_loop(void *conf);
> +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> +void check_all_ports_link_status(uint32_t port_mask);
> +int configure_graph_nodes(uint64_t valid_nodes);
> +
> +int parse_config(const char *q_arg);
> +int parse_node_patterns(const char *q_arg);
> +int validate_node_names(uint64_t *valid_nodes);
> +void cleanup_node_pattern(void);
> +
> +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> +	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt,
> ##args)
> +
> +#endif /* _TESTGRAPH_H_ */
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index 6f84fc31ff..f18c508fa2 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -20,6 +20,7 @@ DPDK Tools User Guides
>      cryptoperf
>      comp_perf
>      testeventdev
> +    testgraph
>      testregex
>      testmldev
>      dts
> diff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst
> new file mode 100644
> index 0000000000..3c1e058724
> --- /dev/null
> +++ b/doc/guides/tools/testgraph.rst
> @@ -0,0 +1,131 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(C) 2023 Marvell International Ltd.
> +
> +dpdk-test-graph Application
> +===========================
> +
> +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK)
> application that allows
> +exercising various graph library features. This application has a generic
> framework to add
> +new test configurations and expand test coverage to verify the functionality
> of graph nodes
> +and observe the graph cluster statistics.
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options:
> +
> +.. code-block:: console
> +
> +   dpdk-test-eventdev [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +The following are the EAL command-line options that can be used in
> conjunction
> +with the ``dpdk-test-graph`` application.
> +See the DPDK Getting Started Guides for more information on these
> options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> +        list of cores to use.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +The following are the application command-line options:
> +
> +* ``-p <n>``
> +
> +        Set the ethdev port mask.
> +
> +* ``-P``
> +
> +        Set the ethdev ports in promiscuous mode.
> +
> +* ``--config <config>``
> +
> +        Set the Rxq configuration.
> +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> +
> +* ``--node-pattern <n>``
> +
> +        Set the node patterns to use in graph creation.
> +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> +
> +* ``--per-port-pool``
> +
> +        Use separate buffer pool per port.
> +
> +* ``--no-numa``
> +
> +        Disable numa awareness.
> +
> +* ``--interactive``
> +
> +        Switch to interactive mode.
> +
> +Running the Tool
> +~~~~~~~~~~~~~~~~
> +
> +Here is the sample command line to run simple iofwd test::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> +
> +Below is a sample command line to punt rx packets to kernel::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> +
> +Interactive mode
> +~~~~~~~~~~~~~~~~
> +
> +Tool uses ``--interactive`` command line option to enter interactive mode
> and use cmdline options
> +to setup the required node configurations, create graph and than start
> graph_walk.
> +
> +
> +testgraph> help
> +
> +Help is available for the following sections:
> +
> +    help control                    : Start and stop graph walk.
> +    help display                    : Displaying port, stats and config information.
> +    help config                     : Configuration information.
> +    help all                        : All of the above sections.
> +
> +testgraph> help all
> +
> +Control forwarding:
> +
> +start graph_walk
> + Start graph_walk on worker threads.
> +
> +stop graph_walk
> + Stop worker threads from running graph_walk.
> +
> +quit
> + Quit to prompt.
> +
> +
> +Display:
> +
> +show node_list
> + Display the list of supported nodes.
> +
> +show graph_stats
> + Display the node statistics of graph cluster.
> +
> +
> +Configuration:
> +
> +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> + Set lcore configuration.
> +
> +create_graph (node0_name,node1_name,...,nodeX_name)
> + Create graph instances using the provided node details.
> +
> +destroy_graph
> + Destroy the graph instances.
> +
> +testgraph>
> --
> 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v2 4/4] app: add testgraph application
  2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
@ 2023-05-09  3:39       ` Vamsi Krishna Attunuru
  2023-05-09  8:53         ` Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-05-09  3:39 UTC (permalink / raw)
  To: Sunil Kumar Kori, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Nithin Kumar Dabilpuram



> -----Original Message-----
> From: Sunil Kumar Kori <skori@marvell.com>
> Sent: Tuesday, May 9, 2023 8:04 AM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> Dabilpuram <ndabilpuram@marvell.com>
> Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> 
> Is there any user guide similar to testpmd ?
> 
   Please refer doc/guides/tools/testgraph.rst for details.

> > -----Original Message-----
> > From: Vamsi Attunuru <vattunuru@marvell.com>
> > Sent: Tuesday, April 25, 2023 6:45 PM
> > To: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> > <jerinj@marvell.com>
> > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> > Dabilpuram <ndabilpuram@marvell.com>
> > Subject: [EXT] [PATCH v2 4/4] app: add testgraph application
> >
> > External Email
> >
> > ----------------------------------------------------------------------
> > Patch adds test-graph application to validate graph
> > and node libraries.
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
> >  app/meson.build                |    1 +
> >  app/test-graph/cmdline.c       |  211 +++++
> >  app/test-graph/cmdline_graph.c |  294 +++++++
> >  app/test-graph/cmdline_graph.h |   19 +
> >  app/test-graph/meson.build     |   14 +
> >  app/test-graph/parameters.c    |  157 ++++
> >  app/test-graph/testgraph.c     | 1426
> ++++++++++++++++++++++++++++++++
> >  app/test-graph/testgraph.h     |   91 ++
> >  doc/guides/tools/index.rst     |    1 +
> >  doc/guides/tools/testgraph.rst |  131 +++
> >  10 files changed, 2345 insertions(+)
> >
> > diff --git a/app/meson.build b/app/meson.build
> > index 74d2420f67..6c7b24e604 100644
> > --- a/app/meson.build
> > +++ b/app/meson.build
> > @@ -22,6 +22,7 @@ apps = [
> >          'test-eventdev',
> >          'test-fib',
> >          'test-flow-perf',
> > +        'test-graph',
> >          'test-gpudev',
> >          'test-mldev',
> >          'test-pipeline',
> > diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> > new file mode 100644
> > index 0000000000..d9474d827a
> > --- /dev/null
> > +++ b/app/test-graph/cmdline.c
> > @@ -0,0 +1,211 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> 
> Please check copyright. I think  it should be " Copyright(C) 2023 Marvell."
> Only.
> > +
> > +#include <stdlib.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_etheraddr.h>
> > +#include <cmdline_parse_ipaddr.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +#include <cmdline_rdline.h>
> > +#include <cmdline_socket.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +static struct cmdline *testgraph_cl;
> > +static cmdline_parse_ctx_t *main_ctx;
> > +
> > +/* *** Help command with introduction. *** */
> > +struct cmd_help_brief_result {
> > +	cmdline_fixed_string_t help;
> > +};
> > +
> > +static void
> > +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct
> cmdline
> > *cl, __rte_unused void *data)
> > +{
> > +	cmdline_printf(cl,
> > +		       "\n"
> > +		       "Help is available for the following sections:\n\n"
> > +		       "    help control                    : Start and stop graph walk.\n"
> > +		       "    help display                    : Displaying port, stats and
> > config "
> > +		       "information.\n"
> > +		       "    help config                     : Configuration information.\n"
> > +		       "    help all                        : All of the above sections.\n\n");
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_brief_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help,
> > "help");
> > +
> > +static cmdline_parse_inst_t cmd_help_brief = {
> > +	.f = cmd_help_brief_parsed,
> > +	.data = NULL,
> > +	.help_str = "help: Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_brief_help,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Help command with help sections. *** */
> > +struct cmd_help_long_result {
> > +	cmdline_fixed_string_t help;
> > +	cmdline_fixed_string_t section;
> > +};
> > +
> > +static void
> > +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	int show_all = 0;
> > +	struct cmd_help_long_result *res = parsed_result;
> > +
> > +	if (!strcmp(res->section, "all"))
> > +		show_all = 1;
> > +
> > +	if (show_all || !strcmp(res->section, "control")) {
> > +
> > +		cmdline_printf(cl, "\n"
> > +				   "Control forwarding:\n"
> > +				   "-------------------\n\n"
> > +
> > +				   "start graph_walk\n"
> > +				   " Start graph_walk on worker threads.\n\n"
> > +
> > +				   "stop graph_walk\n"
> > +				   " Stop worker threads from running
> > graph_walk.\n\n"
> > +
> > +				   "quit\n"
> > +				   "    Quit to prompt.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "display")) {
> > +
> > +		cmdline_printf(cl,
> > +			       "\n"
> > +			       "Display:\n"
> > +			       "--------\n\n"
> > +
> > +			       "show node_list\n"
> > +			       " Display the list of supported nodes.\n\n"
> > +
> > +			       "show graph_stats\n"
> > +			       " Display the node statistics of graph
> > cluster.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "config")) {
> > +		cmdline_printf(cl, "\n"
> > +				   "Configuration:\n"
> > +				   "--------------\n"
> > +				   "set lcore_config
> > (port_id0,rxq0,lcore_idX),..."
> > +				   ".....,(port_idX,rxqX,lcoreidY)\n"
> > +				   " Set lcore configuration.\n\n"
> > +
> > +				   "create_graph
> > (node0_name,node1_name,...,nodeX_name)\n"
> > +				   " Create graph instances using the provided
> > node details.\n\n"
> > +
> > +				   "destroy_graph\n"
> > +				   " Destroy the graph instances.\n\n");
> > +	}
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help,
> > "help");
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_section =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_help_long_result, section, "all#control#display#config");
> > +
> > +static cmdline_parse_inst_t cmd_help_long = {
> > +	.f = cmd_help_long_parsed,
> > +	.data = NULL,
> > +	.help_str = "help all|control|display|config: "
> > +		    "Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_long_help,
> > +			(void *)&cmd_help_long_section,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** QUIT *** */
> > +struct cmd_quit_result {
> > +	cmdline_fixed_string_t quit;
> > +};
> > +
> > +static void
> > +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	cmdline_quit(cl);
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_quit_quit =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> > +
> > +static cmdline_parse_inst_t cmd_quit = {
> > +	.f = cmd_quit_parsed,
> > +	.data = NULL,
> > +	.help_str = "quit: Exit application",
> > +	.tokens = {
> > +			(void *)&cmd_quit_quit,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* list of instructions */
> > +static cmdline_parse_ctx_t builtin_ctx[] = {
> > +	(cmdline_parse_inst_t *)&cmd_help_brief,
> > +	(cmdline_parse_inst_t *)&cmd_help_long,
> > +	(cmdline_parse_inst_t *)&cmd_quit,
> > +	(cmdline_parse_inst_t *)&cmd_show_node_list,
> > +	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
> > +	(cmdline_parse_inst_t *)&cmd_create_graph,
> > +	(cmdline_parse_inst_t *)&cmd_destroy_graph,
> > +	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
> > +	NULL,
> > +};
> > +
> > +int
> > +init_cmdline(void)
> > +{
> > +	unsigned int count;
> > +	unsigned int i;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++)
> > +		count++;
> > +
> > +	/* cmdline expects a NULL terminated array */
> > +	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> > +	if (main_ctx == NULL)
> > +		return -1;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> > +		main_ctx[count] = builtin_ctx[i];
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +prompt_exit(void)
> > +{
> > +	cmdline_quit(testgraph_cl);
> > +}
> > +
> > +/* prompt function, called from main on MAIN lcore */
> > +void
> > +prompt(void)
> > +{
> > +	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> > +	if (testgraph_cl == NULL) {
> > +		fprintf(stderr, "Failed to create stdin based cmdline
> > context\n");
> > +		return;
> > +	}
> > +
> > +	cmdline_interact(testgraph_cl);
> > +	cmdline_stdin_exit(testgraph_cl);
> > +}
> > diff --git a/app/test-graph/cmdline_graph.c b/app/test-
> > graph/cmdline_graph.c
> > new file mode 100644
> > index 0000000000..d66149a224
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.c
> > @@ -0,0 +1,294 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <stdlib.h>
> > +#include <string.h>
> > +
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +/* *** Show supported node details *** */
> > +struct cmd_show_node_list_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t node_list;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_node_list_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> > show, "show");
> > +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> > node_list, "node_list");
> > +
> > +static void
> > +cmd_show_node_list_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			  __rte_unused void *data)
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	rte_node_t id;
> > +
> > +	printf("\n**** Supported Graph Nodes ****\n");
> > +	for (id = 0; id < node_cnt; id++)
> > +		printf("%s\n", rte_node_id_to_name(id));
> > +
> > +	printf("********************************\n");
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_node_list = {
> > +	.f = cmd_show_node_list_parsed,
> > +	.data = NULL,
> > +	.help_str = "show node_list",
> > +	.tokens = {
> > +			(void *)&cmd_show_node_list_show,
> > +			(void *)&cmd_show_node_list_node_list,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Set lcore config *** */
> > +struct cmd_set_lcore_config_result {
> > +	cmdline_fixed_string_t set;
> > +	cmdline_fixed_string_t lcore_config;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> set,
> > "set");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> > lcore_config, "lcore_config");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string
> =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_set_lcore_config_result, token_string,
> > TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct
> > cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	struct cmd_set_lcore_config_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	int ret;
> > +
> > +	/* Parse string */
> > +	ret = parse_config(t_str);
> > +	if (ret) {
> > +		printf(" lcore_config string parse error\n");
> > +		return;
> > +	}
> > +
> > +	validate_config();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_set_lcore_config = {
> > +	.f = cmd_set_lcore_config_parsed,
> > +	.data = NULL,
> > +	.help_str = "set lcore_config "
> > +		    "(port,queue,lcore),[(port,queue,lcore) ...
> > (port,queue,lcore)]",
> > +	.tokens = {
> > +			(void *)&cmd_set_lcore_config_set,
> > +			(void *)&cmd_set_lcore_config_lcore_config,
> > +			(void *)&cmd_set_lcore_config_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Create graph *** */
> > +struct cmd_create_graph_result {
> > +	cmdline_fixed_string_t create_graph;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > create_graph, "create_graph");
> > +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > token_string, TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct
> > cmdline *cl,
> > +			__rte_unused void *data)
> > +{
> > +	struct cmd_create_graph_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	uint64_t valid_nodes = 0;
> > +	int ret;
> > +
> > +	ret = parse_node_patterns(t_str);
> > +	if (ret) {
> > +		printf("parse_node_patterns failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> > +
> > +	ret = validate_node_names(&valid_nodes);
> > +	if (ret) {
> > +		printf("validate_node_names() failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> > +
> > +	nb_conf = ethdev_ports_setup();
> > +
> > +	ethdev_rxq_configure();
> > +	ethdev_txq_configure();
> > +
> > +	ret = configure_graph_nodes(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> err=%d\n",
> > ret);
> > +
> > +	ret = create_graph(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> > +
> > +	stats = create_graph_cluster_stats();
> > +	if (stats == NULL)
> > +		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> > failed\n");
> > +
> > +	check_all_ports_link_status(enabled_port_mask);
> > +	start_eth_ports();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_create_graph = {
> > +	.f = cmd_create_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "create_graph "
> > +		    "[node_name0,node_name1,node_name2 ...
> > node_nameX]",
> > +	.tokens = {
> > +			(void *)&cmd_create_graph_create_graph,
> > +			(void *)&cmd_create_graph_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Destroy graph ****/
> > +struct cmd_destroy_graph_result {
> > +	cmdline_fixed_string_t destroy_graph;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result,
> > destroy_graph, "destroy_graph");
> > +
> > +static void
> > +cmd_destroy_graph_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			 __rte_unused void *data)
> > +{
> > +	uint32_t lcore_id;
> > +
> > +	run_graph_walk = false;
> > +	graph_walk_quit = true;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +		rte_eal_wait_lcore(lcore_id);
> > +
> > +	destroy_graph();
> > +	stop_eth_ports();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_destroy_graph = {
> > +	.f = cmd_destroy_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "destroy_graph",
> > +	.tokens = {
> > +			(void *)&cmd_destroy_graph_destroy_graph,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Start graph_walk ****/
> > +struct cmd_start_graph_walk_result {
> > +	cmdline_fixed_string_t start;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> > start, "start");
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	static bool launch_graph_walk;
> > +
> > +	if (!launch_graph_walk) {
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> > +		launch_graph_walk = true;
> > +	}
> > +
> > +	run_graph_walk = true;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_start_graph_walk = {
> > +	.f = cmd_start_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "start graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_start_graph_walk_start,
> > +			(void *)&cmd_start_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Stop graph_walk ****/
> > +struct cmd_stop_graph_walk_result {
> > +	cmdline_fixed_string_t stop;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> > stop, "stop");
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			   __rte_unused void *data)
> > +{
> > +	run_graph_walk = false;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_stop_graph_walk = {
> > +	.f = cmd_stop_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "stop graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_stop_graph_walk_stop,
> > +			(void *)&cmd_stop_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Show graph_stats ****/
> > +struct cmd_show_graph_stats_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t graph_stats;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > show, "show");
> > +static cmdline_parse_token_string_t
> cmd_show_graph_stats_graph_stats =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > graph_stats, "graph_stats");
> > +
> > +static void
> > +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	if (rte_graph_has_stats_feature()) {
> > +		if (stats)
> > +			rte_graph_cluster_stats_get(stats, 0);
> > +	} else {
> > +		printf(" graph stats feature not enabled in rte_config.\n");
> > +	}
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_graph_stats = {
> > +	.f = cmd_show_graph_stats_parsed,
> > +	.data = NULL,
> > +	.help_str = "show graph_stats",
> > +	.tokens = {
> > +			(void *)&cmd_show_graph_stats_show,
> > +			(void *)&cmd_show_graph_stats_graph_stats,
> > +			NULL,
> > +		},
> > +};
> > diff --git a/app/test-graph/cmdline_graph.h b/app/test-
> > graph/cmdline_graph.h
> > new file mode 100644
> > index 0000000000..2846ff5425
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _CMDLINE_GRAPH_H_
> > +#define _CMDLINE_GRAPH_H_
> > +
> > +extern cmdline_parse_inst_t cmd_show_node_list;
> > +extern cmdline_parse_inst_t cmd_set_lcore_config;
> > +
> > +extern cmdline_parse_inst_t cmd_create_graph;
> > +extern cmdline_parse_inst_t cmd_destroy_graph;
> > +
> > +extern cmdline_parse_inst_t cmd_start_graph_walk;
> > +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> > +
> > +extern cmdline_parse_inst_t cmd_show_graph_stats;
> > +
> > +#endif /* _CMDLINE_GRAPH_H_ */
> > diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> > new file mode 100644
> > index 0000000000..d93802a975
> > --- /dev/null
> > +++ b/app/test-graph/meson.build
> > @@ -0,0 +1,14 @@
> > +# SPDX-License-Identifier: BSD-3-Clause
> > +# Copyright(C) 2023 Marvell International Ltd.
> > +
> > +# override default name to drop the hyphen
> > +name = 'test-graph'
> > +cflags += '-Wno-deprecated-declarations'
> > +sources = files(
> > +        'cmdline.c',
> > +        'cmdline_graph.c',
> > +        'parameters.c',
> > +        'testgraph.c',
> > +)
> > +
> > +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> > diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> > new file mode 100644
> > index 0000000000..b990ca4a1c
> > --- /dev/null
> > +++ b/app/test-graph/parameters.c
> > @@ -0,0 +1,157 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +
> > +#include "testgraph.h"
> > +
> > +static const char short_options[] = "p:" /* portmask */
> > +				    "P"	 /* promiscuous */
> > +				    "i"	 /* interactive */
> > +	;
> > +
> > +#define CMD_LINE_OPT_CONFIG	   "config"
> > +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> > +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> > +#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
> > +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> > +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_CONFIG_NUM,
> > +	CMD_LINE_OPT_NODE_PATTERN_NUM,
> > +	CMD_LINE_OPT_INTERACTIVE_NUM,
> > +	CMD_LINE_OPT_NO_NUMA_NUM,
> > +	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> > +};
> > +
> > +static const struct option lgopts[] = {
> > +	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> > +	{CMD_LINE_OPT_NODE_PATTERN, 1, 0,
> > CMD_LINE_OPT_NODE_PATTERN_NUM},
> > +	{CMD_LINE_OPT_INTERACTIVE, 0, 0,
> > CMD_LINE_OPT_INTERACTIVE_NUM},
> > +	{CMD_LINE_OPT_NO_NUMA, 0, 0,
> > CMD_LINE_OPT_NO_NUMA_NUM},
> > +	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0,
> > CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> > +	{NULL, 0, 0, 0},
> > +};
> > +
> > +/* Display usage */
> > +static void
> > +print_usage(const char *prgname)
> > +{
> > +	fprintf(stderr,
> > +		"%s [EAL options] --"
> > +		" -p PORTMASK"
> > +		" [-P]"
> > +		" [-i]"
> > +		" --config (port,queue,lcore)[,(port,queue,lcore)]"
> > +		" --node-pattern
> > (node_name0,node_name1[,node_nameX)]"
> > +		" [--no-numa]"
> > +		" [--per-port-pool]"
> > +		" [--interactive]"
> > +
> > +		"  -p PORTMASK: Hexadecimal bitmask of ports to
> > configure\n"
> > +		"  -P : Enable promiscuous mode\n"
> > +		"  -i : Enter interactive mode\n"
> > +		"  --config (port,queue,lcore): Rx queue configuration\n"
> > +		"  --node-pattern (node_names): node patterns to create
> > graph\n"
> > +		"  --no-numa: Disable numa awareness\n"
> > +		"  --per-port-pool: Use separate buffer pool per port\n"
> > +		"  --interactive: Enter interactive mode\n",
> > +		prgname);
> > +}
> > +
> > +static int
> > +parse_portmask(const char *portmask)
> > +{
> > +	char *end = NULL;
> > +	unsigned long pm;
> > +
> > +	/* Parse hexadecimal string */
> > +	pm = strtoul(portmask, &end, 16);
> > +	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> > +		return 0;
> > +
> > +	return pm;
> > +}
> > +
> > +/* Parse the argument given in the command line of the application */
> > +int
> > +parse_cmdline_args(int argc, char **argv)
> > +{
> > +	char *prgname = argv[0];
> > +	int option_index;
> > +	char **argvopt;
> > +	int opt, ret;
> > +
> > +	argvopt = argv;
> > +
> > +	/* Error or normal output strings. */
> > +	while ((opt = getopt_long(argc, argvopt, short_options, lgopts,
> > &option_index)) != EOF) {
> > +
> > +		switch (opt) {
> > +		/* Portmask */
> > +		case 'p':
> > +			enabled_port_mask = parse_portmask(optarg);
> > +			if (enabled_port_mask == 0) {
> > +				fprintf(stderr, "Invalid portmask\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case 'P':
> > +			promiscuous_on = 1;
> > +			break;
> > +
> > +		/* Long options */
> > +		case CMD_LINE_OPT_CONFIG_NUM:
> > +			ret = parse_config(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid config\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +		case CMD_LINE_OPT_NODE_PATTERN_NUM:
> > +			ret = parse_node_patterns(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid node_patterns\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case CMD_LINE_OPT_INTERACTIVE_NUM:
> > +		case 'i':
> > +			printf("Interactive-mode selected\n");
> > +			interactive = 1;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_NO_NUMA_NUM:
> > +			numa_on = 0;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> > +			printf("Per port buffer pool is enabled\n");
> > +			per_port_pool = 1;
> > +			break;
> > +
> > +		default:
> > +			print_usage(prgname);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	if (optind >= 0)
> > +		argv[optind - 1] = prgname;
> > +	ret = optind - 1;
> > +	optind = 1; /* Reset getopt lib */
> > +
> > +	return ret;
> > +}
> > diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> > new file mode 100644
> > index 0000000000..aff921acf2
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.c
> > @@ -0,0 +1,1426 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <fnmatch.h>
> > +#include <signal.h>
> > +#include <stdlib.h>
> > +
> > +#include <rte_bus.h>
> > +#include <rte_byteorder.h>
> > +#include <rte_common.h>
> > +#include <rte_dev.h>
> > +#include <rte_eal.h>
> > +#include <rte_ethdev.h>
> > +#include <rte_ether.h>
> > +#include <rte_graph_worker.h>
> > +#include <rte_launch.h>
> > +#include <rte_lcore.h>
> > +#include <rte_log.h>
> > +#include <rte_malloc.h>
> > +#include <rte_mempool.h>
> > +#include <rte_per_lcore.h>
> > +#include <ethdev_rx_priv.h>
> > +#include <ethdev_tx_priv.h>
> > +#include <punt_kernel_priv.h>
> > +#include <kernel_recv_priv.h>
> > +
> > +#include "testgraph.h"
> > +
> > +/* Log type */
> > +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> > +
> > +/*
> > + * Configurable number of RX/TX ring descriptors
> > + */
> > +#define RX_DESC_DEFAULT 1024
> > +#define TX_DESC_DEFAULT 1024
> > +
> > +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> > +#define MAX_RX_QUEUE_PER_PORT 128
> > +
> > +#define MAX_RX_QUEUE_PER_LCORE 16
> > +
> > +#define NB_SOCKETS 8
> > +
> > +/* Static global variables used within this file. */
> > +uint16_t nb_rxd = RX_DESC_DEFAULT;
> > +uint16_t nb_txd = TX_DESC_DEFAULT;
> > +
> > +static volatile bool force_quit;
> > +volatile bool graph_walk_quit;
> > +volatile bool run_graph_walk = true;
> > +
> > +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] =
> {0};
> > +const char **node_patterns;
> > +uint8_t num_patterns;
> > +
> > +uint8_t interactive; /**< interactive mode is off by default */
> > +int promiscuous_on; /**< Ports set in promiscuous mode off by default.
> */
> > +int numa_on = 1;   /**< NUMA is enabled by default. */
> > +int per_port_pool; /**< Use separate buffer pools per port, disabled by
> > default */
> > +int testgraph_logtype; /**< Log type for testgraph logs */
> > +
> > +struct rte_graph_cluster_stats *stats;
> > +
> > +uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +
> > +uint32_t nb_conf;
> > +
> > +struct lcore_rx_queue {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	char node_name[RTE_NODE_NAMESIZE];
> > +};
> > +
> > +/* Lcore conf */
> > +struct lcore_conf {
> > +	uint16_t n_rx_queue;
> > +	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> > +	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> > +	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
> > +
> > +	struct rte_graph *graph;
> > +	char name[RTE_GRAPH_NAMESIZE];
> > +	rte_graph_t graph_id;
> > +} __rte_cache_aligned;
> > +
> > +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> > +
> > +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> > +static struct lcore_params lcore_params_array_default[] = {
> > +	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> > +	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> > +};
> > +
> > +struct lcore_params *lcore_params = lcore_params_array_default;
> > +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +static struct rte_eth_conf port_conf = {
> > +	.rxmode = {
> > +			.mq_mode = RTE_ETH_MQ_RX_RSS,
> > +		},
> > +	.rx_adv_conf = {
> > +			.rss_conf = {
> > +					.rss_key = NULL,
> > +					.rss_hf = RTE_ETH_RSS_IP,
> > +				},
> > +		},
> > +	.txmode = {
> > +			.mq_mode = RTE_ETH_MQ_TX_NONE,
> > +		},
> > +};
> > +
> > +static const struct node_list test_node_list[] = {{{"ethdev_rx",
> "ethdev_tx"},
> > 0, 2},
> > +						{{"ethdev_rx",
> > "punt_kernel"}, 0, 2},
> > +						{{"kernel_recv",
> "ethdev_tx"},
> > 0, 2} };
> > +
> > +static const struct node_list supported_nodes[] = {{{"ethdev_rx"},
> > TEST_GRAPH_ETHDEV_RX_NODE, 1},
> > +						{{"ethdev_tx"},
> > TEST_GRAPH_ETHDEV_TX_NODE, 1},
> > +						{{"punt_kernel"},
> > TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> > +						{{"kernel_recv"},
> > TEST_GRAPH_KERNEL_RECV_NODE, 1},
> > +						{{"ip4_lookup"},
> > TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> > +						{{"ip4_rewrite"},
> > TEST_GRAPH_IP4_REWRITE_NODE, 1},
> > +						{{"pkt_cls"},
> > TEST_GRAPH_PKT_CLS_NODE, 1},
> > +						{{"pkt_drop"},
> > TEST_GRAPH_PKT_DROP_NODE, 1},
> > +						{{"NULL"},
> > TEST_GRAPH_NULL_NODE, 1} };
> > +
> > +static struct rte_mempool
> > *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> > +
> > +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> > +
> > +static int
> > +check_lcore_params(void)
> > +{
> > +	uint8_t queue, lcore;
> > +	int socketid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		queue = lcore_params[i].queue_id;
> > +		if (queue >= MAX_RX_QUEUE_PER_PORT) {
> > +			printf("Invalid queue number: %hhu\n", queue);
> > +			return -1;
> > +		}
> > +		lcore = lcore_params[i].lcore_id;
> > +		if (!rte_lcore_is_enabled(lcore)) {
> > +			printf("Error: lcore %hhu is not enabled in lcore
> > mask\n", lcore);
> > +			return -1;
> > +		}
> > +
> > +		if (lcore == rte_get_main_lcore()) {
> > +			printf("Error: lcore %u is main lcore\n", lcore);
> > +			return -1;
> > +		}
> > +		socketid = rte_lcore_to_socket_id(lcore);
> > +		if ((socketid != 0) && (numa_on == 0)) {
> > +			printf("Warning: lcore %hhu is on socket %d with
> > numa off\n", lcore,
> > +			       socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +check_port_config(void)
> > +{
> > +	uint16_t portid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		portid = lcore_params[i].port_id;
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("Port %u is not enabled in port mask\n",
> > portid);
> > +			return -1;
> > +		}
> > +		if (!rte_eth_dev_is_valid_port(portid)) {
> > +			printf("Port %u is not present on the board\n",
> > portid);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static uint8_t
> > +get_port_n_rx_queues(const uint16_t port)
> > +{
> > +	int queue = -1;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		if (lcore_params[i].port_id == port) {
> > +			if (lcore_params[i].queue_id == queue + 1)
> > +				queue = lcore_params[i].queue_id;
> > +			else
> > +				rte_exit(EXIT_FAILURE, "Queue ids of the
> port
> > %d must be"
> > +					 " in sequence and must start with
> > 0\n",
> > +					 lcore_params[i].port_id);
> > +		}
> > +	}
> > +
> > +	return (uint8_t)(++queue);
> > +}
> > +
> > +static int
> > +init_lcore_rx_queues(void)
> > +{
> > +	uint16_t i, nb_rx_queue;
> > +	uint8_t lcore;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		lcore = lcore_params[i].lcore_id;
> > +		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> > +		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> > +			printf("Error: too many queues (%u) for lcore: %u\n",
> > +			       (unsigned int)nb_rx_queue + 1, (unsigned
> > int)lcore);
> > +			return -1;
> > +		}
> > +
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id =
> > lcore_params[i].port_id;
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id =
> > lcore_params[i].queue_id;
> > +		lcore_conf[lcore].n_rx_queue++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +validate_config(void)
> > +{
> > +	int rc = -1;
> > +
> > +	if (check_lcore_params() < 0) {
> > +		printf("check_lcore_params() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (init_lcore_rx_queues() < 0) {
> > +		printf("init_lcore_rx_queues() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (check_port_config() < 0) {
> > +		printf("check_port_config() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +
> > +exit:
> > +	return rc;
> > +}
> > +
> > +#define MEMPOOL_CACHE_SIZE 256
> > +
> > +/*
> > + * This expression is used to calculate the number of mbufs needed
> > + * depending on user input, taking  into account memory for rx and
> > + * tx hardware rings, cache per lcore and mtable per port per lcore.
> > + * RTE_MAX is used to ensure that NB_MBUF never goes below a
> minimum
> > + * value of 8192
> > + */
> > +#define NB_MBUF(nports)                                                                            \
> > +	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores *
> > RTE_GRAPH_BURST_SIZE +       \
> > +		 nports * nb_tx_queue * nb_txd + nb_lcores *
> > MEMPOOL_CACHE_SIZE),                  \
> > +		8192u)
> > +
> > +static int
> > +init_mem(uint16_t portid, uint32_t nb_mbuf)
> > +{
> > +	uint32_t lcore_id;
> > +	int socketid;
> > +	char s[64];
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		if (numa_on)
> > +			socketid = rte_lcore_to_socket_id(lcore_id);
> > +		else
> > +			socketid = 0;
> > +
> > +		if (socketid >= NB_SOCKETS) {
> > +			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out
> > of range %d\n",
> > +				 socketid, lcore_id, NB_SOCKETS);
> > +		}
> > +
> > +		if (pktmbuf_pool[portid][socketid] == NULL) {
> > +			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid,
> > socketid);
> > +			/* Create a pool with priv size of a cacheline */
> > +			pktmbuf_pool[portid][socketid] =
> > rte_pktmbuf_pool_create(
> > +				s, nb_mbuf, MEMPOOL_CACHE_SIZE,
> > RTE_CACHE_LINE_SIZE,
> > +				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> > +			if (pktmbuf_pool[portid][socketid] == NULL)
> > +				rte_exit(EXIT_FAILURE, "Cannot init mbuf
> > pool on socket %d\n",
> > +					 socketid);
> > +			else
> > +				printf("Allocated mbuf pool on socket %d\n",
> > socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +check_all_ports_link_status(uint32_t port_mask)
> > +{
> > +#define CHECK_INTERVAL 100 /* 100ms */
> > +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> > +	uint8_t count, all_ports_up, print_flag = 0;
> > +	struct rte_eth_link link;
> > +	uint16_t portid;
> > +	int ret;
> > +	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> > +
> > +	printf("\nChecking link status");
> > +	fflush(stdout);
> > +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> > +		if (force_quit)
> > +			return;
> > +		all_ports_up = 1;
> > +		RTE_ETH_FOREACH_DEV(portid) {
> > +			if (force_quit)
> > +				return;
> > +			if ((port_mask & (1 << portid)) == 0)
> > +				continue;
> > +			memset(&link, 0, sizeof(link));
> > +			ret = rte_eth_link_get_nowait(portid, &link);
> > +			if (ret < 0) {
> > +				all_ports_up = 0;
> > +				if (print_flag == 1)
> > +					printf("Port %u link get failed: %s\n",
> > portid,
> > +					       rte_strerror(-ret));
> > +				continue;
> > +			}
> > +			/* Print link status if flag set */
> > +			if (print_flag == 1) {
> > +				rte_eth_link_to_str(link_status_text,
> > sizeof(link_status_text),
> > +						    &link);
> > +				printf("Port %d %s\n", portid,
> > link_status_text);
> > +				continue;
> > +			}
> > +			/* Clear all_ports_up flag if any link down */
> > +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> > +				all_ports_up = 0;
> > +				break;
> > +			}
> > +		}
> > +		/* After finally printing all link status, get out */
> > +		if (print_flag == 1)
> > +			break;
> > +
> > +		if (all_ports_up == 0) {
> > +			printf(".");
> > +			fflush(stdout);
> > +			rte_delay_ms(CHECK_INTERVAL);
> > +		}
> > +
> > +		/* Set the print_flag if all ports up or timeout */
> > +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> > +			print_flag = 1;
> > +			printf("Done\n");
> > +		}
> > +	}
> > +}
> > +
> > +static void
> > +signal_handler(int signum)
> > +{
> > +	if (signum == SIGINT || signum == SIGTERM) {
> > +		printf("\n\nSignal %d received, preparing to exit...\n",
> > signum);
> > +		force_quit = true;
> > +	}
> > +	prompt_exit();
> > +}
> > +
> > +int
> > +graph_main_loop(void *conf)
> > +{
> > +	struct lcore_conf *qconf;
> > +	struct rte_graph *graph;
> > +	uint32_t lcore_id;
> > +
> > +	RTE_SET_USED(conf);
> > +
> > +	lcore_id = rte_lcore_id();
> > +	qconf = &lcore_conf[lcore_id];
> > +	graph = qconf->graph;
> > +
> > +	if (!graph) {
> > +		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to
> > do\n", lcore_id);
> > +		return 0;
> > +	}
> > +
> > +	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u, graph
> > %s(%p)\n", lcore_id,
> > +		qconf->name, graph);
> > +
> > +	while (likely(!force_quit && !graph_walk_quit)) {
> > +		if (likely(run_graph_walk))
> > +			rte_graph_walk(graph);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +struct rte_graph_cluster_stats *
> > +create_graph_cluster_stats(void)
> > +{
> > +	struct rte_graph_cluster_stats_param s_param;
> > +	struct rte_graph_cluster_stats *stat;
> > +	const char *pattern = "worker_*";
> > +
> > +	/* 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;
> > +
> > +	stat = rte_graph_cluster_stats_create(&s_param);
> > +	if (stat == NULL)
> > +		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> > +
> > +	return stat;
> > +}
> > +
> > +static void
> > +print_stats(void)
> > +{
> > +	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> > +	const char clr[] = {27, '[', '2', 'J', '\0'};
> > +
> > +	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);
> > +}
> > +
> > +int
> > +parse_config(const char *q_arg)
> > +{
> > +	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE,
> > _NUM_FLD };
> > +	unsigned long int_fld[_NUM_FLD];
> > +	const char *p, *p0 = q_arg;
> > +	char *str_fld[_NUM_FLD];
> > +	uint32_t size;
> > +	char s[256];
> > +	char *end;
> > +	int i;
> > +
> > +	nb_lcore_params = 0;
> > +
> > +	while ((p = strchr(p0, '(')) != NULL) {
> > +		++p;
> > +		p0 = strchr(p, ')');
> > +		if (p0 == NULL)
> > +			goto exit;
> > +
> > +		size = p0 - p;
> > +		if (size >= sizeof(s))
> > +			goto exit;
> > +
> > +		memcpy(s, p, size);
> > +		s[size] = '\0';
> > +		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') !=
> > _NUM_FLD)
> > +			goto exit;
> > +		for (i = 0; i < _NUM_FLD; i++) {
> > +			errno = 0;
> > +			int_fld[i] = strtoul(str_fld[i], &end, 0);
> > +			if (errno != 0 || end == str_fld[i])
> > +				goto exit;
> > +		}
> > +
> > +		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> > +			printf("Exceeded max number of lcore params:
> > %hu\n", nb_lcore_params);
> > +			goto exit;
> > +		}
> > +
> > +		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS ||
> > int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> > +			printf("Invalid port/lcore id\n");
> > +			goto exit;
> > +		}
> > +
> > +		lcore_params_array[nb_lcore_params].port_id =
> > (uint8_t)int_fld[FLD_PORT];
> > +		lcore_params_array[nb_lcore_params].queue_id =
> > (uint8_t)int_fld[FLD_QUEUE];
> > +		lcore_params_array[nb_lcore_params].lcore_id =
> > (uint8_t)int_fld[FLD_LCORE];
> > +		++nb_lcore_params;
> > +	}
> > +	lcore_params = lcore_params_array;
> > +
> > +	return 0;
> > +exit:
> > +	/* Revert to default config */
> > +	lcore_params = lcore_params_array_default;
> > +	nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +	return -1;
> > +}
> > +
> > +int
> > +parse_node_patterns(const char *q_arg)
> > +{
> > +	const char *p, *p0 = q_arg;
> > +	int ret = -EINVAL;
> > +	uint32_t size;
> > +
> > +	num_patterns = 0;
> > +
> > +	p = strchr(p0, '(');
> > +	if (p != NULL) {
> > +		++p;
> > +		while ((p0 = strchr(p, ',')) != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> > +			p = p0 + 1;
> > +		}
> > +
> > +		p0 = strchr(p, ')');
> > +		if (p0 != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> > +		} else {
> > +			goto exit;
> > +		}
> > +	} else {
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static void
> > +set_default_node_pattern(void)
> > +{
> > +	uint16_t idx;
> > +
> > +	for (idx = 0; idx < test_node_list[0].size; idx++)
> > +		strcpy(node_pattern[num_patterns++],
> > test_node_list[0].nodes[idx]);
> > +}
> > +
> > +int
> > +validate_node_names(uint64_t *valid_nodes)
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	bool pattern_matched = false;
> > +	rte_node_t id = 0;
> > +	int ret = -EINVAL;
> > +	uint16_t idx, i, j;
> > +
> > +	for (idx = 0; idx < num_patterns; idx++) {
> > +		for (id = 0; id < node_cnt; id++) {
> > +			if (strncmp(node_pattern[idx],
> > rte_node_id_to_name(id),
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +				break;
> > +		}
> > +		if (node_cnt == id) {
> > +			printf("Invalid node name passed\n");
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +		idx = 0;
> > +		if (test_node_list[i].size == num_patterns) {
> > +			for (j = 0; j < num_patterns; j++) {
> > +				if (strncmp(node_pattern[j],
> > test_node_list[i].nodes[j],
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +					idx++;
> > +			}
> > +			if (idx == num_patterns)
> > +				pattern_matched = true;
> > +		}
> > +	}
> > +
> > +	if (!pattern_matched) {
> > +		printf("Unsupported node pattern passed\n\n");
> > +		printf("Test supported node patterns are:\n");
> > +		for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +			printf("(");
> > +			for (j = 0; j < (test_node_list[i].size - 1); j++)
> > +				printf("%s,", test_node_list[i].nodes[j]);
> > +			printf("%s", test_node_list[i].nodes[j]);
> > +			printf(")\n");
> > +		}
> > +
> > +		return ret;
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> > +		for (j = 0; j < num_patterns; j++) {
> > +			if (strncmp(node_pattern[j],
> > supported_nodes[i].nodes[0],
> > +				    RTE_GRAPH_NAMESIZE) == 0) {
> > +				*valid_nodes |= supported_nodes[i].test_id;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +cleanup_node_pattern(void)
> > +{
> > +	while (num_patterns) {
> > +		memset(node_pattern[num_patterns - 1], 0,
> > RTE_GRAPH_NAMESIZE);
> > +		num_patterns--;
> > +	}
> > +}
> > +
> > +static int
> > +ethdev_tx_node_configure(void)
> > +{
> > +	struct ethdev_tx_node_main *tx_node_data;
> > +	struct rte_node_register *tx_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t port_id;
> > +	uint32_t id;
> > +
> > +	tx_node_data = ethdev_tx_node_data_get();
> > +	tx_node = ethdev_tx_node_get();
> > +
> > +	RTE_ETH_FOREACH_DEV(port_id) {
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", port_id);
> > +			continue;
> > +		}
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create a per port tx node from base node */
> > +		snprintf(name, sizeof(name), "%u", port_id);
> > +		id = rte_node_clone(tx_node->id, name);
> > +		tx_node_data->nodes[port_id] = id;
> > +
> > +		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +punt_kernel_node_configure(void)
> > +{
> > +	struct rte_node_register *punt_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	punt_node = punt_kernel_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore punt_kernel node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(punt_node->id, name);
> > +		strcpy(qconf->punt_kernel_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("punt_kernel node %s-%s: is at %u\n", punt_node-
> > >name, name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +kernel_recv_node_configure(void)
> > +{
> > +	struct rte_node_register *recv_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	recv_node = kernel_recv_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore kernel_recv node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(recv_node->id, name);
> > +		strcpy(qconf->kernel_recv_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("kernel_recv node %s-%s: is at %u\n", recv_node-
> > >name, name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf,
> uint16_t
> > nb_confs)
> > +{
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t i, j, port_id;
> > +	uint32_t id;
> > +
> > +	for (i = 0; i < nb_confs; i++) {
> > +		port_id = conf[i].port_id;
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create node for each rx port queue pair */
> > +		for (j = 0; j < conf[i].num_rx_queues; j++) {
> > +			struct ethdev_rx_node_main *rx_node_data;
> > +			struct rte_node_register *rx_node;
> > +			ethdev_rx_node_elem_t *elem;
> > +
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			rx_node = ethdev_rx_node_get();
> > +			snprintf(name, sizeof(name), "%u-%u", port_id, j);
> > +			/* Clone a new rx node with same edges as parent */
> > +			id = rte_node_clone(rx_node->id, name);
> > +			if (id == RTE_NODE_ID_INVALID)
> > +				return -EIO;
> > +
> > +			/* Add it to list of ethdev rx nodes for lookup */
> > +			elem = malloc(sizeof(ethdev_rx_node_elem_t));
> > +			if (elem == NULL)
> > +				return -ENOMEM;
> > +			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> > +			elem->ctx.port_id = port_id;
> > +			elem->ctx.queue_id = j;
> > +			elem->nid = id;
> > +			elem->next = rx_node_data->head;
> > +			rx_node_data->head = elem;
> > +
> > +			printf("ethdev:: Rx node %s-%s: is at %u\n",
> rx_node-
> > >name, name, id);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct ethdev_rx_node_main *rx_node_data;
> > +	ethdev_rx_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	uint16_t queue, queue_id, port_id;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint32_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +		port_id = qconf->rx_queue_list[queue].port_id;
> > +		queue_id = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id,
> > queue_id);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Prepare the actual name of the cloned node */
> > +		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"punt_kernel-*"};
> > +	uint16_t queueid, portid, queue;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	rte_node_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +
> > +		portid = qconf->rx_queue_list[queue].port_id;
> > +		queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid,
> > queueid);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		next_node = qconf->punt_kernel_node_name;
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], next_node);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct kernel_recv_node_main *rx_node_data;
> > +	kernel_recv_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = kernel_recv_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.recv_info->cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint16_t port_id;
> > +	rte_node_t id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	id = rte_node_from_name(qconf->kernel_recv_node_name);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], qconf-
> > >kernel_recv_node_name);
> > +
> > +	port_id = lcore_id;
> > +
> > +	if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +		/* Use available port_id */
> > +		RTE_ETH_FOREACH_DEV(port_id) {
> > +			if ((enabled_port_mask & (1 << port_id)) != 0)
> > +				break;
> > +		}
> > +	}
> > +
> > +	if (!rte_eth_dev_is_valid_port(port_id)) {
> > +		printf("Port %u is not present on the board\n", port_id);
> > +		return -1;
> > +	}
> > +
> > +	/* Prepare the actual name of the cloned node */
> > +	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +	/* Update ethdev_rx node edges */
> > +	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node,
> 1);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], name);
> > +
> > +	/* Update node_next details */
> > +	update_kernel_recv_node_next(id, pattern[0]);
> > +
> > +	return 0;
> > +}
> > +
> > +uint32_t
> > +ethdev_ports_setup(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint32_t nb_tx_queue, nb_lcores;
> > +	uint32_t nb_ports, nb_conf = 0;
> > +	uint8_t nb_rx_queue;
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	nb_ports = rte_eth_dev_count_avail();
> > +	nb_lcores = rte_lcore_count();
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		/* Init port */
> > +		printf("Initializing port %d ... ", portid);
> > +		fflush(stdout);
> > +
> > +		nb_rx_queue = get_port_n_rx_queues(portid);
> > +		nb_tx_queue = nb_lcores;
> > +		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> > +			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> > +		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n",
> > nb_rx_queue, nb_tx_queue);
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		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 !=
> > +		    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, 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_rx_queue,
> > nb_tx_queue, &local_port_conf);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Cannot configure device:
> > err=%d, port=%d\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=%d\n",
> > +				 ret, portid);
> > +
> > +		/* Init memory */
> > +		if (!per_port_pool)
> > +			ret = init_mem(0, NB_MBUF(nb_ports));
> > +		else
> > +			ret = init_mem(portid, NB_MBUF(1));
> > +
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> > +
> > +		/* Setup ethdev node config */
> > +		ethdev_conf[nb_conf].port_id = portid;
> > +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> > +		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> > +		if (!per_port_pool)
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> > +
> > +		else
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> > +		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> > +
> > +		nb_conf++;
> > +	}
> > +
> > +	return nb_conf;
> > +}
> > +
> > +void
> > +ethdev_rxq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint16_t queueid, portid;
> > +	struct lcore_conf *qconf;
> > +	uint8_t queue, socketid;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +		qconf = &lcore_conf[lcore_id];
> > +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> > +		fflush(stdout);
> > +
> > +		/* Init RX queues */
> > +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +			struct rte_eth_rxconf rxq_conf;
> > +
> > +			portid = qconf->rx_queue_list[queue].port_id;
> > +			queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> > +			fflush(stdout);
> > +
> > +			rte_eth_dev_info_get(portid, &dev_info);
> > +			rxq_conf = dev_info.default_rxconf;
> > +			rxq_conf.offloads = port_conf.rxmode.offloads;
> > +			if (!per_port_pool)
> > +				ret = rte_eth_rx_queue_setup(portid,
> > queueid, nb_rxd, socketid,
> > +							     &rxq_conf,
> > pktmbuf_pool[0][socketid]);
> > +			else
> > +				ret = rte_eth_rx_queue_setup(portid,
> > queueid, nb_rxd, socketid,
> > +							     &rxq_conf,
> > +
> > pktmbuf_pool[portid][socketid]);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +
> > +			snprintf(qconf->rx_queue_list[queue].node_name,
> > RTE_NODE_NAMESIZE,
> > +				 "ethdev_rx-%u-%u", portid, queueid);
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +void
> > +ethdev_txq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	struct rte_eth_txconf *txconf;
> > +	uint16_t queueid, portid;
> > +	uint32_t lcore_id;
> > +	uint8_t socketid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		/* Init one TX queue per (lcore,port) pair */
> > +		queueid = 0;
> > +		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +			if (rte_lcore_is_enabled(lcore_id) == 0)
> > +				continue;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("txq=%u,%d,%d ", lcore_id, queueid,
> socketid);
> > +			fflush(stdout);
> > +
> > +			txconf = &dev_info.default_txconf;
> > +			txconf->offloads = local_port_conf.txmode.offloads;
> > +			ret = rte_eth_tx_queue_setup(portid, queueid,
> > nb_txd, socketid, txconf);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +			queueid++;
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +int
> > +configure_graph_nodes(uint64_t valid_nodes)
> > +{
> > +	int ret = 0;
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> > +		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> > +		if (ret) {
> > +			printf("ethdev_rx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> > +		ret = ethdev_tx_node_configure();
> > +		if (ret) {
> > +			printf("ethdev_tx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> > +		ret = punt_kernel_node_configure();
> > +		if (ret) {
> > +			printf("punt_kernel_node_configure: err=%d\n",
> > ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
> > +		ret = kernel_recv_node_configure();
> > +		if (ret) {
> > +			printf("kernel_recv_node_configure: err=%d\n",
> ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static int
> > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> > +{
> > +	int ret = 0;
> > +
> > +	num_patterns = 0;
> > +
> > +	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_ETHDEV_RX_NODE)) {
> > +		ret = link_ethdev_rx_to_tx_node(lcore_id);
> > +		if (ret) {
> > +			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE |
> > TEST_GRAPH_PUNT_KERNEL_NODE)) {
> > +		link_ethdev_rx_to_punt_kernel_node(lcore_id);
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_KERNEL_RECV_NODE)) {
> > +		link_kernel_recv_to_ethdev_tx_node(lcore_id);
> > +	} else {
> > +		printf("Invalid node map\n");
> > +		ret = -EINVAL;
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +void
> > +start_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +
> > +		ret = rte_eth_dev_start(portid);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d,
> > port=%d\n", ret, portid);
> > +
> > +		if (promiscuous_on)
> > +			rte_eth_promiscuous_enable(portid);
> > +	}
> > +}
> > +
> > +void
> > +stop_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +		printf("Closing port %d...", portid);
> > +		ret = rte_eth_dev_stop(portid);
> > +		if (ret != 0)
> > +			printf("Failed to stop port %u: %s\n", portid,
> > rte_strerror(-ret));
> > +		rte_eth_dev_close(portid);
> > +	}
> > +}
> > +
> > +int
> > +create_graph(uint64_t valid_nodes)
> > +{
> > +	struct rte_graph_param graph_conf;
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	node_patterns = malloc(MAX_NODE_PATTERNS *
> > sizeof(*node_patterns));
> > +	if (!node_patterns)
> > +		return -ENOMEM;
> > +
> > +	memset(&graph_conf, 0, sizeof(graph_conf));
> > +	graph_conf.node_patterns = node_patterns;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		rte_graph_t graph_id;
> > +		rte_edge_t i;
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Skip graph creation if no source exists */
> > +		if (!qconf->n_rx_queue)
> > +			continue;
> > +
> > +		ret = link_graph_nodes(valid_nodes, lcore_id);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "link_graph_nodes():
> > failed\n");
> > +
> > +		for (i = 0; i < num_patterns; i++) {
> > +			graph_conf.node_patterns[i] = node_pattern[i];
> > +			printf("%s,", graph_conf.node_patterns[i]);
> > +		}
> > +		printf("\n");
> > +
> > +		graph_conf.nb_node_patterns = num_patterns;
> > +
> > +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> > +
> > +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> > lcore_id);
> > +
> > +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> > +		if (graph_id == RTE_GRAPH_ID_INVALID)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_create():
> graph_id
> > invalid for lcore%u\n",
> > +				 lcore_id);
> > +
> > +		qconf->graph_id = graph_id;
> > +		qconf->graph = rte_graph_lookup(qconf->name);
> > +
> > +		if (!qconf->graph)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph
> > %s not found\n",
> > +				 qconf->name);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +destroy_graph(void)
> > +{
> > +	uint32_t lcore_id;
> > +	rte_graph_t id;
> > +	int ret;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +	{
> > +		if (lcore_conf[lcore_id].graph) {
> > +			id =
> > rte_graph_from_name(lcore_conf[lcore_id].name);
> > +			if (rte_graph_destroy(id)) {
> > +				printf("graph_id %u destroy failed.\n", id);
> > +				ret = -1;
> > +			}
> > +		}
> > +	}
> > +
> > +	if (node_patterns)
> > +		free(node_patterns);
> > +
> > +	return ret;
> > +}
> > +
> > +int
> > +main(int argc, char **argv)
> > +{
> > +	uint64_t valid_nodes;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	graph_walk_quit = false;
> > +	force_quit = false;
> > +	interactive = 0;
> > +
> > +	node_patterns = NULL;
> > +
> > +	signal(SIGINT, signal_handler);
> > +	signal(SIGTERM, signal_handler);
> > +
> > +	testgraph_logtype = rte_log_register("testgraph");
> > +	if (testgraph_logtype < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot register log type");
> > +
> > +	set_default_node_pattern();
> > +
> > +	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> > +
> > +	ret = rte_eal_init(argc, argv);
> > +	if (ret < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n",
> > rte_strerror(rte_errno));
> > +	argc -= ret;
> > +	argv += ret;
> > +
> > +	if (argc > 1) {
> > +		ret = parse_cmdline_args(argc, argv);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Invalid command line
> > parameters\n");
> > +	}
> > +
> > +#ifdef RTE_LIB_CMDLINE
> > +	if (init_cmdline() != 0)
> > +		rte_exit(EXIT_FAILURE, "Could not initialise cmdline
> > context.\n");
> > +
> > +	if (interactive == 1) {
> > +		prompt();
> > +	} else
> > +#endif
> > +	{
> > +		if (validate_config() < 0)
> > +			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> > +
> > +		ret = validate_node_names(&valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "validate_node_names:
> > err=%d\n", ret);
> > +
> > +		nb_conf = ethdev_ports_setup();
> > +
> > +		ethdev_rxq_configure();
> > +
> > +		ethdev_txq_configure();
> > +
> > +		ret = configure_graph_nodes(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> > err=%d\n", ret);
> > +
> > +		ret = create_graph(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n",
> > ret);
> > +
> > +		stats = create_graph_cluster_stats();
> > +		if (stats == NULL)
> > +			rte_exit(EXIT_FAILURE,
> "create_graph_cluster_stats()
> > failed\n");
> > +
> > +		check_all_ports_link_status(enabled_port_mask);
> > +
> > +		start_eth_ports();
> > +
> > +		/* Launch per-lcore init on every worker lcore */
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> > +
> > +		/* Accumulate and print stats on main until exit */
> > +		if (rte_graph_has_stats_feature())
> > +			print_stats();
> > +
> > +		/* Wait for worker cores to exit */
> > +		RTE_LCORE_FOREACH_WORKER(lcore_id) {
> > +			ret = rte_eal_wait_lcore(lcore_id);
> > +			if (ret < 0)
> > +				break;
> > +		}
> > +
> > +		ret = destroy_graph();
> > +
> > +		stop_eth_ports();
> > +	}
> > +
> > +	/* clean up the EAL */
> > +	ret = rte_eal_cleanup();
> > +	if (ret != 0)
> > +		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-
> > ret));
> > +
> > +	return EXIT_SUCCESS;
> > +}
> > diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> > new file mode 100644
> > index 0000000000..ea1f8ef4fa
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.h
> > @@ -0,0 +1,91 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _TESTGRAPH_H_
> > +#define _TESTGRAPH_H_
> > +
> > +#include <stdbool.h>
> > +
> > +#include <rte_ethdev.h>
> > +#include <rte_graph.h>
> > +#include <rte_node_eth_api.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +
> > +
> > +#define MAX_LCORE_PARAMS 1024
> > +#define MAX_NODE_PATTERNS 128
> > +
> > +#ifndef BIT_ULL
> > +#define BIT_ULL(nr) (1ULL << (nr))
> > +#endif
> > +
> > +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> > +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> > +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> > +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> > +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> > +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> > +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> > +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> > +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> > +
> > +static volatile bool force_quit;
> > +
> > +extern struct rte_node_ethdev_config
> ethdev_conf[RTE_MAX_ETHPORTS];
> > +extern uint32_t enabled_port_mask;
> > +extern uint32_t nb_conf;
> > +
> > +extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by
> > default. */
> > +extern uint8_t interactive;	   /**< interactive mode is disabled by
> > default. */
> > +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +extern int numa_on;		   /**< NUMA is enabled by default.
> */
> > +extern int per_port_pool;
> > +
> > +extern volatile bool graph_walk_quit;
> > +extern volatile bool run_graph_walk;
> > +extern struct rte_graph_cluster_stats *stats;
> > +
> > +extern char
> node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> > +extern uint8_t num_patterns;
> > +
> > +struct node_list {
> > +	const char *nodes[MAX_NODE_PATTERNS];
> > +	uint64_t test_id;
> > +	uint8_t size;
> > +};
> > +
> > +struct lcore_params {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	uint8_t lcore_id;
> > +} __rte_cache_aligned;
> > +
> > +void prompt(void);
> > +void prompt_exit(void);
> > +int init_cmdline(void);
> > +int validate_config(void);
> > +int parse_cmdline_args(int argc, char **argv);
> > +uint32_t ethdev_ports_setup(void);
> > +void ethdev_rxq_configure(void);
> > +void ethdev_txq_configure(void);
> > +void start_eth_ports(void);
> > +void stop_eth_ports(void);
> > +int create_graph(uint64_t valid_nodes);
> > +int destroy_graph(void);
> > +int graph_main_loop(void *conf);
> > +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> > +void check_all_ports_link_status(uint32_t port_mask);
> > +int configure_graph_nodes(uint64_t valid_nodes);
> > +
> > +int parse_config(const char *q_arg);
> > +int parse_node_patterns(const char *q_arg);
> > +int validate_node_names(uint64_t *valid_nodes);
> > +void cleanup_node_pattern(void);
> > +
> > +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> > +	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt,
> > ##args)
> > +
> > +#endif /* _TESTGRAPH_H_ */
> > diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> > index 6f84fc31ff..f18c508fa2 100644
> > --- a/doc/guides/tools/index.rst
> > +++ b/doc/guides/tools/index.rst
> > @@ -20,6 +20,7 @@ DPDK Tools User Guides
> >      cryptoperf
> >      comp_perf
> >      testeventdev
> > +    testgraph
> >      testregex
> >      testmldev
> >      dts
> > diff --git a/doc/guides/tools/testgraph.rst
> b/doc/guides/tools/testgraph.rst
> > new file mode 100644
> > index 0000000000..3c1e058724
> > --- /dev/null
> > +++ b/doc/guides/tools/testgraph.rst
> > @@ -0,0 +1,131 @@
> > +..  SPDX-License-Identifier: BSD-3-Clause
> > +    Copyright(C) 2023 Marvell International Ltd.
> > +
> > +dpdk-test-graph Application
> > +===========================
> > +
> > +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK)
> > application that allows
> > +exercising various graph library features. This application has a generic
> > framework to add
> > +new test configurations and expand test coverage to verify the
> functionality
> > of graph nodes
> > +and observe the graph cluster statistics.
> > +
> > +Running the Application
> > +-----------------------
> > +
> > +The application has a number of command line options:
> > +
> > +.. code-block:: console
> > +
> > +   dpdk-test-eventdev [EAL Options] -- [application options]
> > +
> > +EAL Options
> > +~~~~~~~~~~~
> > +
> > +The following are the EAL command-line options that can be used in
> > conjunction
> > +with the ``dpdk-test-graph`` application.
> > +See the DPDK Getting Started Guides for more information on these
> > options.
> > +
> > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > +
> > +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> > +        list of cores to use.
> > +
> > +Application Options
> > +~~~~~~~~~~~~~~~~~~~
> > +
> > +The following are the application command-line options:
> > +
> > +* ``-p <n>``
> > +
> > +        Set the ethdev port mask.
> > +
> > +* ``-P``
> > +
> > +        Set the ethdev ports in promiscuous mode.
> > +
> > +* ``--config <config>``
> > +
> > +        Set the Rxq configuration.
> > +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> > +
> > +* ``--node-pattern <n>``
> > +
> > +        Set the node patterns to use in graph creation.
> > +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> > +
> > +* ``--per-port-pool``
> > +
> > +        Use separate buffer pool per port.
> > +
> > +* ``--no-numa``
> > +
> > +        Disable numa awareness.
> > +
> > +* ``--interactive``
> > +
> > +        Switch to interactive mode.
> > +
> > +Running the Tool
> > +~~~~~~~~~~~~~~~~
> > +
> > +Here is the sample command line to run simple iofwd test::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> > +
> > +Below is a sample command line to punt rx packets to kernel::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> > +
> > +Interactive mode
> > +~~~~~~~~~~~~~~~~
> > +
> > +Tool uses ``--interactive`` command line option to enter interactive mode
> > and use cmdline options
> > +to setup the required node configurations, create graph and than start
> > graph_walk.
> > +
> > +
> > +testgraph> help
> > +
> > +Help is available for the following sections:
> > +
> > +    help control                    : Start and stop graph walk.
> > +    help display                    : Displaying port, stats and config information.
> > +    help config                     : Configuration information.
> > +    help all                        : All of the above sections.
> > +
> > +testgraph> help all
> > +
> > +Control forwarding:
> > +
> > +start graph_walk
> > + Start graph_walk on worker threads.
> > +
> > +stop graph_walk
> > + Stop worker threads from running graph_walk.
> > +
> > +quit
> > + Quit to prompt.
> > +
> > +
> > +Display:
> > +
> > +show node_list
> > + Display the list of supported nodes.
> > +
> > +show graph_stats
> > + Display the node statistics of graph cluster.
> > +
> > +
> > +Configuration:
> > +
> > +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> > + Set lcore configuration.
> > +
> > +create_graph (node0_name,node1_name,...,nodeX_name)
> > + Create graph instances using the provided node details.
> > +
> > +destroy_graph
> > + Destroy the graph instances.
> > +
> > +testgraph>
> > --
> > 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH 4/4] app: add testgraph application
  2023-04-21  6:02 ` [PATCH 4/4] app: add testgraph application Vamsi Attunuru
@ 2023-05-09  6:09   ` Jerin Jacob
  0 siblings, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-05-09  6:09 UTC (permalink / raw)
  To: Vamsi Attunuru, Haiyue Wang, Liang, Cunming, Zhirun Yan
  Cc: dev, jerinj, ndabilpuram

On Fri, Apr 21, 2023 at 11:33 AM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> Patch adds test-graph application to validate graph
> and node libraries.
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>

+ haiyue.wang@intel.com  cunming.liang@intel.com zhirun.yan@intel.com
for review to align with new multi-core dispatch model.


> ---
>  app/meson.build                |    1 +
>  app/test-graph/cmdline.c       |  212 ++++++
>  app/test-graph/cmdline_graph.c |  297 ++++++++
>  app/test-graph/cmdline_graph.h |   19 +
>  app/test-graph/meson.build     |   17 +
>  app/test-graph/parameters.c    |  157 ++++
>  app/test-graph/testgraph.c     | 1309 ++++++++++++++++++++++++++++++++
>  app/test-graph/testgraph.h     |   92 +++
>  doc/guides/tools/index.rst     |    1 +
>  doc/guides/tools/testgraph.rst |  131 ++++
>  10 files changed, 2236 insertions(+)
>
> diff --git a/app/meson.build b/app/meson.build
> index 74d2420f67..6c7b24e604 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -22,6 +22,7 @@ apps = [
>          'test-eventdev',
>          'test-fib',
>          'test-flow-perf',
> +        'test-graph',
>          'test-gpudev',
>          'test-mldev',
>          'test-pipeline',
> diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> new file mode 100644
> index 0000000000..a07a8a24f9
> --- /dev/null
> +++ b/app/test-graph/cmdline.c
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <stdlib.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_etheraddr.h>
> +#include <cmdline_parse_ipaddr.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_rdline.h>
> +#include <cmdline_socket.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +static struct cmdline *testgraph_cl;
> +static cmdline_parse_ctx_t *main_ctx;
> +
> +/* *** Help command with introduction. *** */
> +struct cmd_help_brief_result {
> +       cmdline_fixed_string_t help;
> +};
> +
> +static void
> +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
> +{
> +       cmdline_printf(cl,
> +                      "\n"
> +                      "Help is available for the following sections:\n\n"
> +                      "    help control                    : Start and stop graph walk.\n"
> +                      "    help display                    : Displaying port, stats and config "
> +                      "information.\n"
> +                      "    help config                     : Configuration information.\n"
> +                      "    help all                        : All of the above sections.\n\n");
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_brief_help =
> +       TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help, "help");
> +
> +static cmdline_parse_inst_t cmd_help_brief = {
> +       .f = cmd_help_brief_parsed,
> +       .data = NULL,
> +       .help_str = "help: Show help",
> +       .tokens = {
> +                       (void *)&cmd_help_brief_help,
> +                       NULL,
> +               },
> +};
> +
> +/* *** Help command with help sections. *** */
> +struct cmd_help_long_result {
> +       cmdline_fixed_string_t help;
> +       cmdline_fixed_string_t section;
> +};
> +
> +static void
> +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl, __rte_unused void *data)
> +{
> +       int show_all = 0;
> +       struct cmd_help_long_result *res = parsed_result;
> +
> +       if (!strcmp(res->section, "all"))
> +               show_all = 1;
> +
> +       if (show_all || !strcmp(res->section, "control")) {
> +
> +               cmdline_printf(cl, "\n"
> +                                  "Control forwarding:\n"
> +                                  "-------------------\n\n"
> +
> +                                  "start graph_walk\n"
> +                                  " Start graph_walk on worker threads.\n\n"
> +
> +                                  "stop graph_walk\n"
> +                                  " Stop worker threads from running graph_walk.\n\n"
> +
> +                                  "quit\n"
> +                                  "    Quit to prompt.\n\n");
> +       }
> +
> +       if (show_all || !strcmp(res->section, "display")) {
> +
> +               cmdline_printf(cl,
> +                              "\n"
> +                              "Display:\n"
> +                              "--------\n\n"
> +
> +                              "show node_list\n"
> +                              " Display the list of supported nodes.\n\n"
> +
> +                              "show graph_stats\n"
> +                              " Display the node statistics of graph cluster.\n\n");
> +       }
> +
> +       if (show_all || !strcmp(res->section, "config")) {
> +               cmdline_printf(cl, "\n"
> +                                  "Configuration:\n"
> +                                  "--------------\n"
> +                                  "set lcore_config (port_id0,rxq0,lcore_idX),..."
> +                                  ".....,(port_idX,rxqX,lcoreidY)\n"
> +                                  " Set lcore configuration.\n\n"
> +
> +                                  "create_graph (node0_name,node1_name,...,nodeX_name)\n"
> +                                  " Create graph instances using the provided node details.\n\n"
> +
> +                                  "destroy_graph\n"
> +                                  " Destroy the graph instances.\n\n");
> +       }
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_long_help =
> +       TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help, "help");
> +
> +static cmdline_parse_token_string_t cmd_help_long_section = TOKEN_STRING_INITIALIZER(
> +       struct cmd_help_long_result, section, "all#control#display#config");
> +
> +static cmdline_parse_inst_t cmd_help_long = {
> +       .f = cmd_help_long_parsed,
> +       .data = NULL,
> +       .help_str = "help all|control|display|config: "
> +                   "Show help",
> +       .tokens = {
> +                       (void *)&cmd_help_long_help,
> +                       (void *)&cmd_help_long_section,
> +                       NULL,
> +               },
> +};
> +
> +/* *** QUIT *** */
> +struct cmd_quit_result {
> +       cmdline_fixed_string_t quit;
> +};
> +
> +static void
> +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)
> +{
> +       cmdline_quit(cl);
> +       cl_quit = 1;
> +}
> +
> +static cmdline_parse_token_string_t cmd_quit_quit =
> +       TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> +
> +static cmdline_parse_inst_t cmd_quit = {
> +       .f = cmd_quit_parsed,
> +       .data = NULL,
> +       .help_str = "quit: Exit application",
> +       .tokens = {
> +                       (void *)&cmd_quit_quit,
> +                       NULL,
> +               },
> +};
> +
> +/* list of instructions */
> +static cmdline_parse_ctx_t builtin_ctx[] = {
> +       (cmdline_parse_inst_t *)&cmd_help_brief,
> +       (cmdline_parse_inst_t *)&cmd_help_long,
> +       (cmdline_parse_inst_t *)&cmd_quit,
> +       (cmdline_parse_inst_t *)&cmd_show_node_list,
> +       (cmdline_parse_inst_t *)&cmd_set_lcore_config,
> +       (cmdline_parse_inst_t *)&cmd_create_graph,
> +       (cmdline_parse_inst_t *)&cmd_destroy_graph,
> +       (cmdline_parse_inst_t *)&cmd_start_graph_walk,
> +       (cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> +       (cmdline_parse_inst_t *)&cmd_show_graph_stats,
> +       NULL,
> +};
> +
> +int
> +init_cmdline(void)
> +{
> +       unsigned int count;
> +       unsigned int i;
> +
> +       count = 0;
> +       for (i = 0; builtin_ctx[i] != NULL; i++)
> +               count++;
> +
> +       /* cmdline expects a NULL terminated array */
> +       main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> +       if (main_ctx == NULL)
> +               return -1;
> +
> +       count = 0;
> +       for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> +               main_ctx[count] = builtin_ctx[i];
> +
> +       return 0;
> +}
> +
> +void
> +prompt_exit(void)
> +{
> +       cmdline_quit(testgraph_cl);
> +}
> +
> +/* prompt function, called from main on MAIN lcore */
> +void
> +prompt(void)
> +{
> +       testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> +       if (testgraph_cl == NULL) {
> +               fprintf(stderr, "Failed to create stdin based cmdline context\n");
> +               return;
> +       }
> +
> +       cmdline_interact(testgraph_cl);
> +       cmdline_stdin_exit(testgraph_cl);
> +}
> diff --git a/app/test-graph/cmdline_graph.c b/app/test-graph/cmdline_graph.c
> new file mode 100644
> index 0000000000..1c9fd94355
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.c
> @@ -0,0 +1,297 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +/* *** Show supported node details *** */
> +struct cmd_show_node_list_result {
> +       cmdline_fixed_string_t show;
> +       cmdline_fixed_string_t node_list;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_node_list_show =
> +       TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, show, "show");
> +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> +       TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, node_list, "node_list");
> +
> +static void
> +cmd_show_node_list_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                         __rte_unused void *data)
> +{
> +       rte_node_t node_cnt = rte_node_max_count();
> +       rte_node_t id;
> +
> +       printf("\n**** Supported Graph Nodes ****\n");
> +       for (id = 0; id < node_cnt; id++)
> +               printf("%s\n", rte_node_id_to_name(id));
> +
> +       printf("********************************\n");
> +}
> +
> +cmdline_parse_inst_t cmd_show_node_list = {
> +       .f = cmd_show_node_list_parsed,
> +       .data = NULL,
> +       .help_str = "show node_list",
> +       .tokens = {
> +                       (void *)&cmd_show_node_list_show,
> +                       (void *)&cmd_show_node_list_node_list,
> +                       NULL,
> +               },
> +};
> +
> +/* *** Set lcore config *** */
> +struct cmd_set_lcore_config_result {
> +       cmdline_fixed_string_t set;
> +       cmdline_fixed_string_t lcore_config;
> +       cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> +       TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set, "set");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =
> +       TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, lcore_config, "lcore_config");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string = TOKEN_STRING_INITIALIZER(
> +       struct cmd_set_lcore_config_result, token_string, TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
> +                           __rte_unused void *data)
> +{
> +       struct cmd_set_lcore_config_result *res = parsed_result;
> +       const char *t_str = res->token_string;
> +       int ret;
> +
> +       /* Parse string */
> +       ret = parse_config(t_str);
> +       if (ret) {
> +               printf(" lcore_config string parse error\n");
> +               return;
> +       }
> +
> +       validate_config();
> +}
> +
> +cmdline_parse_inst_t cmd_set_lcore_config = {
> +       .f = cmd_set_lcore_config_parsed,
> +       .data = NULL,
> +       .help_str = "set lcore_config "
> +                   "(port,queue,lcore),[(port,queue,lcore) ... (port,queue,lcore)]",
> +       .tokens = {
> +                       (void *)&cmd_set_lcore_config_set,
> +                       (void *)&cmd_set_lcore_config_lcore_config,
> +                       (void *)&cmd_set_lcore_config_token_string,
> +                       NULL,
> +               },
> +};
> +
> +/* *** Create graph *** */
> +struct cmd_create_graph_result {
> +       cmdline_fixed_string_t create_graph;
> +       cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> +       TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, create_graph, "create_graph");
> +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> +       TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, token_string, TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct cmdline *cl,
> +                       __rte_unused void *data)
> +{
> +       struct cmd_create_graph_result *res = parsed_result;
> +       const char *t_str = res->token_string;
> +       uint64_t valid_nodes = 0;
> +       int ret;
> +
> +       ret = parse_node_patterns(t_str);
> +       if (ret) {
> +               printf("parse_node_patterns failed\n");
> +               cleanup_node_pattern();
> +               return;
> +       }
> +
> +       ret = validate_node_names(&valid_nodes);
> +       if (ret) {
> +               printf("validate_node_names() failed\n");
> +               cleanup_node_pattern();
> +               return;
> +       }
> +
> +       nb_conf = ethdev_ports_setup();
> +
> +       ethdev_rxq_configure();
> +       ethdev_txq_configure();
> +
> +       ret = configure_graph_nodes(valid_nodes);
> +       if (ret) {
> +               printf("configure_graph_nodes() failed\n");
> +               cleanup_node_pattern();
> +               return;
> +       }
> +
> +       start_eth_ports();
> +       check_all_ports_link_status(enabled_port_mask);
> +
> +       ret = create_graph(node_pattern, num_patterns);
> +       if (ret)
> +               rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> +
> +       stats = create_graph_cluster_stats();
> +       if (stats == NULL)
> +               rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
> +}
> +
> +cmdline_parse_inst_t cmd_create_graph = {
> +       .f = cmd_create_graph_parsed,
> +       .data = NULL,
> +       .help_str = "create_graph "
> +                   "[node_name0,node_name1,node_name2 ... node_nameX]",
> +       .tokens = {
> +                       (void *)&cmd_create_graph_create_graph,
> +                       (void *)&cmd_create_graph_token_string,
> +                       NULL,
> +               },
> +};
> +
> +/**** Destroy graph ****/
> +struct cmd_destroy_graph_result {
> +       cmdline_fixed_string_t destroy_graph;
> +};
> +
> +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =
> +       TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result, destroy_graph, "destroy_graph");
> +
> +static void
> +cmd_destroy_graph_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                        __rte_unused void *data)
> +{
> +       uint32_t lcore_id;
> +
> +       run_graph_walk = false;
> +       graph_walk_quit = true;
> +
> +       RTE_LCORE_FOREACH_WORKER(lcore_id)
> +               rte_eal_wait_lcore(lcore_id);
> +
> +       destroy_graph();
> +       stop_eth_ports();
> +}
> +
> +cmdline_parse_inst_t cmd_destroy_graph = {
> +       .f = cmd_destroy_graph_parsed,
> +       .data = NULL,
> +       .help_str = "destroy_graph",
> +       .tokens = {
> +                       (void *)&cmd_destroy_graph_destroy_graph,
> +                       NULL,
> +               },
> +};
> +
> +/**** Start graph_walk ****/
> +struct cmd_start_graph_walk_result {
> +       cmdline_fixed_string_t start;
> +       cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> +       TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, start, "start");
> +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =
> +       TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, graph_walk, "graph_walk");
> +
> +static void
> +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                           __rte_unused void *data)
> +{
> +       static bool launch_graph_walk;
> +
> +       if (!launch_graph_walk) {
> +               rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
> +               launch_graph_walk = true;
> +       }
> +
> +       run_graph_walk = true;
> +}
> +
> +cmdline_parse_inst_t cmd_start_graph_walk = {
> +       .f = cmd_start_graph_walk_parsed,
> +       .data = NULL,
> +       .help_str = "start graph_walk",
> +       .tokens = {
> +                       (void *)&cmd_start_graph_walk_start,
> +                       (void *)&cmd_start_graph_walk_graph_walk,
> +                       NULL,
> +               },
> +};
> +
> +/**** Stop graph_walk ****/
> +struct cmd_stop_graph_walk_result {
> +       cmdline_fixed_string_t stop;
> +       cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> +       TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, stop, "stop");
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =
> +       TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, graph_walk, "graph_walk");
> +
> +static void
> +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                          __rte_unused void *data)
> +{
> +       run_graph_walk = false;
> +}
> +
> +cmdline_parse_inst_t cmd_stop_graph_walk = {
> +       .f = cmd_stop_graph_walk_parsed,
> +       .data = NULL,
> +       .help_str = "stop graph_walk",
> +       .tokens = {
> +                       (void *)&cmd_stop_graph_walk_stop,
> +                       (void *)&cmd_stop_graph_walk_graph_walk,
> +                       NULL,
> +               },
> +};
> +
> +/**** Show graph_stats ****/
> +struct cmd_show_graph_stats_result {
> +       cmdline_fixed_string_t show;
> +       cmdline_fixed_string_t graph_stats;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> +       TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, show, "show");
> +static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =
> +       TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, graph_stats, "graph_stats");
> +
> +static void
> +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                           __rte_unused void *data)
> +{
> +       if (rte_graph_has_stats_feature()) {
> +               if (stats)
> +                       rte_graph_cluster_stats_get(stats, 0);
> +       } else {
> +               printf(" graph stats feature not enabled in rte_config.\n");
> +       }
> +}
> +
> +cmdline_parse_inst_t cmd_show_graph_stats = {
> +       .f = cmd_show_graph_stats_parsed,
> +       .data = NULL,
> +       .help_str = "show graph_stats",
> +       .tokens = {
> +                       (void *)&cmd_show_graph_stats_show,
> +                       (void *)&cmd_show_graph_stats_graph_stats,
> +                       NULL,
> +               },
> +};
> diff --git a/app/test-graph/cmdline_graph.h b/app/test-graph/cmdline_graph.h
> new file mode 100644
> index 0000000000..2846ff5425
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _CMDLINE_GRAPH_H_
> +#define _CMDLINE_GRAPH_H_
> +
> +extern cmdline_parse_inst_t cmd_show_node_list;
> +extern cmdline_parse_inst_t cmd_set_lcore_config;
> +
> +extern cmdline_parse_inst_t cmd_create_graph;
> +extern cmdline_parse_inst_t cmd_destroy_graph;
> +
> +extern cmdline_parse_inst_t cmd_start_graph_walk;
> +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> +
> +extern cmdline_parse_inst_t cmd_show_graph_stats;
> +
> +#endif /* _CMDLINE_GRAPH_H_ */
> diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> new file mode 100644
> index 0000000000..a969a91183
> --- /dev/null
> +++ b/app/test-graph/meson.build
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(C) 2023 Marvell International Ltd.
> +
> +# override default name to drop the hyphen
> +name = 'test-graph'
> +cflags += '-Wno-deprecated-declarations'
> +sources = files(
> +        'cmdline.c',
> +        'cmdline_graph.c',
> +        'parameters.c',
> +        'testgraph.c',
> +)
> +
> +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> +
> +# Driver-specific commands are located in driver directories.
> +includes = include_directories('.')
> diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> new file mode 100644
> index 0000000000..b990ca4a1c
> --- /dev/null
> +++ b/app/test-graph/parameters.c
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +
> +#include "testgraph.h"
> +
> +static const char short_options[] = "p:" /* portmask */
> +                                   "P"  /* promiscuous */
> +                                   "i"  /* interactive */
> +       ;
> +
> +#define CMD_LINE_OPT_CONFIG       "config"
> +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> +#define CMD_LINE_OPT_NO_NUMA      "no-numa"
> +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> +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_CONFIG_NUM,
> +       CMD_LINE_OPT_NODE_PATTERN_NUM,
> +       CMD_LINE_OPT_INTERACTIVE_NUM,
> +       CMD_LINE_OPT_NO_NUMA_NUM,
> +       CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> +};
> +
> +static const struct option lgopts[] = {
> +       {CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> +       {CMD_LINE_OPT_NODE_PATTERN, 1, 0, CMD_LINE_OPT_NODE_PATTERN_NUM},
> +       {CMD_LINE_OPT_INTERACTIVE, 0, 0, CMD_LINE_OPT_INTERACTIVE_NUM},
> +       {CMD_LINE_OPT_NO_NUMA, 0, 0, CMD_LINE_OPT_NO_NUMA_NUM},
> +       {CMD_LINE_OPT_PER_PORT_POOL, 0, 0, CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> +       {NULL, 0, 0, 0},
> +};
> +
> +/* Display usage */
> +static void
> +print_usage(const char *prgname)
> +{
> +       fprintf(stderr,
> +               "%s [EAL options] --"
> +               " -p PORTMASK"
> +               " [-P]"
> +               " [-i]"
> +               " --config (port,queue,lcore)[,(port,queue,lcore)]"
> +               " --node-pattern (node_name0,node_name1[,node_nameX)]"
> +               " [--no-numa]"
> +               " [--per-port-pool]"
> +               " [--interactive]"
> +
> +               "  -p PORTMASK: Hexadecimal bitmask of ports to configure\n"
> +               "  -P : Enable promiscuous mode\n"
> +               "  -i : Enter interactive mode\n"
> +               "  --config (port,queue,lcore): Rx queue configuration\n"
> +               "  --node-pattern (node_names): node patterns to create graph\n"
> +               "  --no-numa: Disable numa awareness\n"
> +               "  --per-port-pool: Use separate buffer pool per port\n"
> +               "  --interactive: Enter interactive mode\n",
> +               prgname);
> +}
> +
> +static int
> +parse_portmask(const char *portmask)
> +{
> +       char *end = NULL;
> +       unsigned long pm;
> +
> +       /* Parse hexadecimal string */
> +       pm = strtoul(portmask, &end, 16);
> +       if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> +               return 0;
> +
> +       return pm;
> +}
> +
> +/* Parse the argument given in the command line of the application */
> +int
> +parse_cmdline_args(int argc, char **argv)
> +{
> +       char *prgname = argv[0];
> +       int option_index;
> +       char **argvopt;
> +       int opt, ret;
> +
> +       argvopt = argv;
> +
> +       /* Error or normal output strings. */
> +       while ((opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index)) != EOF) {
> +
> +               switch (opt) {
> +               /* Portmask */
> +               case 'p':
> +                       enabled_port_mask = parse_portmask(optarg);
> +                       if (enabled_port_mask == 0) {
> +                               fprintf(stderr, "Invalid portmask\n");
> +                               print_usage(prgname);
> +                               return -1;
> +                       }
> +                       break;
> +
> +               case 'P':
> +                       promiscuous_on = 1;
> +                       break;
> +
> +               /* Long options */
> +               case CMD_LINE_OPT_CONFIG_NUM:
> +                       ret = parse_config(optarg);
> +                       if (ret) {
> +                               fprintf(stderr, "Invalid config\n");
> +                               print_usage(prgname);
> +                               return -1;
> +                       }
> +                       break;
> +               case CMD_LINE_OPT_NODE_PATTERN_NUM:
> +                       ret = parse_node_patterns(optarg);
> +                       if (ret) {
> +                               fprintf(stderr, "Invalid node_patterns\n");
> +                               print_usage(prgname);
> +                               return -1;
> +                       }
> +                       break;
> +
> +               case CMD_LINE_OPT_INTERACTIVE_NUM:
> +               case 'i':
> +                       printf("Interactive-mode selected\n");
> +                       interactive = 1;
> +                       break;
> +
> +               case CMD_LINE_OPT_NO_NUMA_NUM:
> +                       numa_on = 0;
> +                       break;
> +
> +               case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> +                       printf("Per port buffer pool is enabled\n");
> +                       per_port_pool = 1;
> +                       break;
> +
> +               default:
> +                       print_usage(prgname);
> +                       return -1;
> +               }
> +       }
> +
> +       if (optind >= 0)
> +               argv[optind - 1] = prgname;
> +       ret = optind - 1;
> +       optind = 1; /* Reset getopt lib */
> +
> +       return ret;
> +}
> diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> new file mode 100644
> index 0000000000..b236b907c7
> --- /dev/null
> +++ b/app/test-graph/testgraph.c
> @@ -0,0 +1,1309 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fnmatch.h>
> +#include <signal.h>
> +#include <stdlib.h>
> +
> +#include <rte_bus.h>
> +#include <rte_byteorder.h>
> +#include <rte_common.h>
> +#include <rte_dev.h>
> +#include <rte_eal.h>
> +#include <rte_ethdev.h>
> +#include <rte_ether.h>
> +#include <rte_graph_worker.h>
> +#include <rte_launch.h>
> +#include <rte_lcore.h>
> +#include <rte_log.h>
> +#include <rte_malloc.h>
> +#include <rte_mempool.h>
> +#include <rte_per_lcore.h>
> +#include <ethdev_rx_priv.h>
> +#include <ethdev_tx_priv.h>
> +#include <punt_kernel_priv.h>
> +
> +#include "testgraph.h"
> +
> +/* Log type */
> +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> +
> +/*
> + * Configurable number of RX/TX ring descriptors
> + */
> +#define RX_DESC_DEFAULT 1024
> +#define TX_DESC_DEFAULT 1024
> +
> +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> +#define MAX_RX_QUEUE_PER_PORT 128
> +
> +#define MAX_RX_QUEUE_PER_LCORE 16
> +
> +#define NB_SOCKETS 8
> +
> +/* Static global variables used within this file. */
> +uint16_t nb_rxd = RX_DESC_DEFAULT;
> +uint16_t nb_txd = TX_DESC_DEFAULT;
> +
> +static volatile bool force_quit;
> +volatile bool graph_walk_quit;
> +
> +volatile bool run_graph_walk = true;
> +
> +uint8_t cl_quit;
> +
> +uint8_t interactive; /**< interactive mode is off by default */
> +
> +const char **node_patterns;
> +
> +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};
> +uint8_t num_patterns;
> +
> +int promiscuous_on; /**< Ports set in promiscuous mode off by default. */
> +
> +int numa_on = 1;   /**< NUMA is enabled by default. */
> +int per_port_pool; /**< Use separate buffer pools per port; disabled */
> +                  /**< by default */
> +
> +int testgraph_logtype; /**< Log type for testpmd logs */
> +
> +struct rte_graph_cluster_stats *stats;
> +
> +/* Mask of enabled ports */
> +uint32_t enabled_port_mask;
> +
> +uint32_t nb_conf;
> +
> +struct lcore_rx_queue {
> +       uint16_t port_id;
> +       uint8_t queue_id;
> +       char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +struct lcore_tx_queue {
> +       char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +/* Lcore conf */
> +struct lcore_conf {
> +       uint16_t n_rx_queue;
> +       struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> +       char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> +
> +       struct rte_graph *graph;
> +       char name[RTE_GRAPH_NAMESIZE];
> +       rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +
> +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> +static struct lcore_params lcore_params_array_default[] = {
> +       {0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> +       {2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> +};
> +
> +struct lcore_params *lcore_params = lcore_params_array_default;
> +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +static struct rte_eth_conf port_conf = {
> +       .rxmode = {
> +                       .mq_mode = RTE_ETH_MQ_RX_RSS,
> +               },
> +       .rx_adv_conf = {
> +                       .rss_conf = {
> +                                       .rss_key = NULL,
> +                                       .rss_hf = RTE_ETH_RSS_IP,
> +                               },
> +               },
> +       .txmode = {
> +                       .mq_mode = RTE_ETH_MQ_TX_NONE,
> +               },
> +};
> +
> +static const struct node_list test_node_list[] = {{{"ethdev_rx", "ethdev_tx"}, 0, 2},
> +                                               {{"ethdev_rx", "punt_kernel"}, 0, 2} };
> +
> +static const struct node_list supported_nodes[] = {{{"ethdev_rx"}, TEST_GRAPH_ETHDEV_RX_NODE, 1},
> +                                               {{"ethdev_tx"}, TEST_GRAPH_ETHDEV_TX_NODE, 1},
> +                                               {{"punt_kernel"}, TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> +                                               {{"kernel_recv"}, TEST_GRAPH_KERNEL_RECV_NODE, 1},
> +                                               {{"ip4_lookup"}, TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> +                                               {{"ip4_rewrite"}, TEST_GRAPH_IP4_REWRITE_NODE, 1},
> +                                               {{"pkt_cls"}, TEST_GRAPH_PKT_CLS_NODE, 1},
> +                                               {{"pkt_drop"}, TEST_GRAPH_PKT_DROP_NODE, 1},
> +                                               {{"NULL"}, TEST_GRAPH_NULL_NODE, 1} };
> +
> +static struct rte_mempool *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> +
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +
> +static int
> +check_lcore_params(void)
> +{
> +       uint8_t queue, lcore;
> +       int socketid;
> +       uint16_t i;
> +
> +       for (i = 0; i < nb_lcore_params; ++i) {
> +               queue = lcore_params[i].queue_id;
> +               if (queue >= MAX_RX_QUEUE_PER_PORT) {
> +                       printf("Invalid queue number: %hhu\n", queue);
> +                       return -1;
> +               }
> +               lcore = lcore_params[i].lcore_id;
> +               if (!rte_lcore_is_enabled(lcore)) {
> +                       printf("Error: lcore %hhu is not enabled in lcore mask\n", lcore);
> +                       return -1;
> +               }
> +
> +               if (lcore == rte_get_main_lcore()) {
> +                       printf("Error: lcore %u is main lcore\n", lcore);
> +                       return -1;
> +               }
> +               socketid = rte_lcore_to_socket_id(lcore);
> +               if ((socketid != 0) && (numa_on == 0)) {
> +                       printf("Warning: lcore %hhu is on socket %d with numa off\n", lcore,
> +                              socketid);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +check_port_config(void)
> +{
> +       uint16_t portid;
> +       uint16_t i;
> +
> +       for (i = 0; i < nb_lcore_params; ++i) {
> +               portid = lcore_params[i].port_id;
> +               if ((enabled_port_mask & (1 << portid)) == 0) {
> +                       printf("Port %u is not enabled in port mask\n", portid);
> +                       return -1;
> +               }
> +               if (!rte_eth_dev_is_valid_port(portid)) {
> +                       printf("Port %u is not present on the board\n", portid);
> +                       return -1;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static uint8_t
> +get_port_n_rx_queues(const uint16_t port)
> +{
> +       int queue = -1;
> +       uint16_t i;
> +
> +       for (i = 0; i < nb_lcore_params; ++i) {
> +               if (lcore_params[i].port_id == port) {
> +                       if (lcore_params[i].queue_id == queue + 1)
> +                               queue = lcore_params[i].queue_id;
> +                       else
> +                               rte_exit(EXIT_FAILURE,
> +                                        "Queue ids of the port %d must be"
> +                                        " in sequence and must start with 0\n",
> +                                        lcore_params[i].port_id);
> +               }
> +       }
> +
> +       return (uint8_t)(++queue);
> +}
> +
> +static int
> +init_lcore_rx_queues(void)
> +{
> +       uint16_t i, nb_rx_queue;
> +       uint8_t lcore;
> +
> +       for (i = 0; i < nb_lcore_params; ++i) {
> +               lcore = lcore_params[i].lcore_id;
> +               nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> +               if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> +                       printf("Error: too many queues (%u) for lcore: %u\n",
> +                              (unsigned int)nb_rx_queue + 1, (unsigned int)lcore);
> +                       return -1;
> +               }
> +
> +               lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id = lcore_params[i].port_id;
> +               lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id = lcore_params[i].queue_id;
> +               lcore_conf[lcore].n_rx_queue++;
> +       }
> +
> +       return 0;
> +}
> +
> +int
> +validate_config(void)
> +{
> +       int rc = -1;
> +
> +       if (check_lcore_params() < 0) {
> +               printf("check_lcore_params() failed\n");
> +               goto exit;
> +       }
> +
> +       if (init_lcore_rx_queues() < 0) {
> +               printf("init_lcore_rx_queues() failed\n");
> +               goto exit;
> +       }
> +
> +       if (check_port_config() < 0) {
> +               printf("check_port_config() failed\n");
> +               goto exit;
> +       }
> +
> +       return 0;
> +
> +exit:
> +       return rc;
> +}
> +
> +#define MEMPOOL_CACHE_SIZE 256
> +
> +/*
> + * This expression is used to calculate the number of mbufs needed
> + * depending on user input, taking  into account memory for rx and
> + * tx hardware rings, cache per lcore and mtable per port per lcore.
> + * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum
> + * value of 8192
> + */
> +#define NB_MBUF(nports)                                                                            \
> +       RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores * RTE_GRAPH_BURST_SIZE +       \
> +                nports * nb_tx_queue * nb_txd + nb_lcores * MEMPOOL_CACHE_SIZE),                  \
> +               8192u)
> +
> +static int
> +init_mem(uint16_t portid, uint32_t nb_mbuf)
> +{
> +       uint32_t lcore_id;
> +       int socketid;
> +       char s[64];
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +
> +               if (numa_on)
> +                       socketid = rte_lcore_to_socket_id(lcore_id);
> +               else
> +                       socketid = 0;
> +
> +               if (socketid >= NB_SOCKETS) {
> +                       rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out of range %d\n",
> +                                socketid, lcore_id, NB_SOCKETS);
> +               }
> +
> +               if (pktmbuf_pool[portid][socketid] == NULL) {
> +                       snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid, socketid);
> +                       /* Create a pool with priv size of a cacheline */
> +                       pktmbuf_pool[portid][socketid] = rte_pktmbuf_pool_create(
> +                               s, nb_mbuf, MEMPOOL_CACHE_SIZE, RTE_CACHE_LINE_SIZE,
> +                               RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> +                       if (pktmbuf_pool[portid][socketid] == NULL)
> +                               rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n",
> +                                        socketid);
> +                       else
> +                               printf("Allocated mbuf pool on socket %d\n", socketid);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +       uint8_t count, all_ports_up, print_flag = 0;
> +       struct rte_eth_link link;
> +       uint16_t portid;
> +       int ret;
> +       char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> +
> +       printf("\nChecking link status");
> +       fflush(stdout);
> +       for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +               if (force_quit)
> +                       return;
> +               all_ports_up = 1;
> +               RTE_ETH_FOREACH_DEV(portid) {
> +                       if (force_quit)
> +                               return;
> +                       if ((port_mask & (1 << portid)) == 0)
> +                               continue;
> +                       memset(&link, 0, sizeof(link));
> +                       ret = rte_eth_link_get_nowait(portid, &link);
> +                       if (ret < 0) {
> +                               all_ports_up = 0;
> +                               if (print_flag == 1)
> +                                       printf("Port %u link get failed: %s\n", portid,
> +                                              rte_strerror(-ret));
> +                               continue;
> +                       }
> +                       /* Print link status if flag set */
> +                       if (print_flag == 1) {
> +                               rte_eth_link_to_str(link_status_text, sizeof(link_status_text),
> +                                                   &link);
> +                               printf("Port %d %s\n", portid, link_status_text);
> +                               continue;
> +                       }
> +                       /* Clear all_ports_up flag if any link down */
> +                       if (link.link_status == RTE_ETH_LINK_DOWN) {
> +                               all_ports_up = 0;
> +                               break;
> +                       }
> +               }
> +               /* After finally printing all link status, get out */
> +               if (print_flag == 1)
> +                       break;
> +
> +               if (all_ports_up == 0) {
> +                       printf(".");
> +                       fflush(stdout);
> +                       rte_delay_ms(CHECK_INTERVAL);
> +               }
> +
> +               /* Set the print_flag if all ports up or timeout */
> +               if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +                       print_flag = 1;
> +                       printf("Done\n");
> +               }
> +       }
> +}
> +
> +static void
> +signal_handler(int signum)
> +{
> +       if (signum == SIGINT || signum == SIGTERM) {
> +               printf("\n\nSignal %d received, preparing to exit...\n", signum);
> +               force_quit = true;
> +       }
> +       prompt_exit();
> +}
> +
> +int
> +graph_main_loop(void *conf)
> +{
> +       struct lcore_conf *qconf;
> +       struct rte_graph *graph;
> +       uint32_t lcore_id;
> +
> +       RTE_SET_USED(conf);
> +
> +       lcore_id = rte_lcore_id();
> +       qconf = &lcore_conf[lcore_id];
> +       graph = qconf->graph;
> +
> +       if (!graph) {
> +               RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
> +               return 0;
> +       }
> +
> +       RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
> +               qconf->name, graph);
> +
> +       while (likely(!force_quit & !graph_walk_quit)) {
> +               if (likely(run_graph_walk))
> +                       rte_graph_walk(graph);
> +       }
> +
> +       return 0;
> +}
> +
> +struct rte_graph_cluster_stats *
> +create_graph_cluster_stats(void)
> +{
> +       struct rte_graph_cluster_stats_param s_param;
> +       struct rte_graph_cluster_stats *stat;
> +       const char *pattern = "worker_*";
> +
> +       /* 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;
> +
> +       stat = rte_graph_cluster_stats_create(&s_param);
> +       if (stat == NULL)
> +               rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> +
> +       return stat;
> +}
> +
> +static void
> +print_stats(void)
> +{
> +       const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> +       const char clr[] = {27, '[', '2', 'J', '\0'};
> +
> +       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);
> +}
> +
> +int
> +parse_config(const char *q_arg)
> +{
> +       enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE, _NUM_FLD };
> +       unsigned long int_fld[_NUM_FLD];
> +       const char *p, *p0 = q_arg;
> +       char *str_fld[_NUM_FLD];
> +       uint32_t size;
> +       char s[256];
> +       char *end;
> +       int i;
> +
> +       nb_lcore_params = 0;
> +
> +       while ((p = strchr(p0, '(')) != NULL) {
> +               ++p;
> +               p0 = strchr(p, ')');
> +               if (p0 == NULL)
> +                       goto exit;
> +
> +               size = p0 - p;
> +               if (size >= sizeof(s))
> +                       goto exit;
> +
> +               memcpy(s, p, size);
> +               s[size] = '\0';
> +               if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD)
> +                       goto exit;
> +               for (i = 0; i < _NUM_FLD; i++) {
> +                       errno = 0;
> +                       int_fld[i] = strtoul(str_fld[i], &end, 0);
> +                       if (errno != 0 || end == str_fld[i])
> +                               goto exit;
> +               }
> +
> +               if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> +                       printf("Exceeded max number of lcore params: %hu\n", nb_lcore_params);
> +                       goto exit;
> +               }
> +
> +               if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS || int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> +                       printf("Invalid port/lcore id\n");
> +                       goto exit;
> +               }
> +
> +               lcore_params_array[nb_lcore_params].port_id = (uint8_t)int_fld[FLD_PORT];
> +               lcore_params_array[nb_lcore_params].queue_id = (uint8_t)int_fld[FLD_QUEUE];
> +               lcore_params_array[nb_lcore_params].lcore_id = (uint8_t)int_fld[FLD_LCORE];
> +               ++nb_lcore_params;
> +       }
> +       lcore_params = lcore_params_array;
> +
> +       return 0;
> +exit:
> +       /* Revert to default config */
> +       lcore_params = lcore_params_array_default;
> +       nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +       return -1;
> +}
> +
> +int
> +parse_node_patterns(const char *q_arg)
> +{
> +       const char *p, *p0 = q_arg;
> +       int ret = -EINVAL;
> +       uint32_t size;
> +
> +       num_patterns = 0;
> +
> +       p = strchr(p0, '(');
> +       if (p != NULL) {
> +               ++p;
> +               while ((p0 = strchr(p, ',')) != NULL) {
> +                       size = p0 - p;
> +                       if (size >= RTE_NODE_NAMESIZE)
> +                               goto exit;
> +
> +                       if (num_patterns >= MAX_NODE_PATTERNS) {
> +                               printf("Too many nodes passed.\n");
> +                               goto exit;
> +                       }
> +
> +                       memcpy(node_pattern[num_patterns++], p, size);
> +                       p = p0 + 1;
> +               }
> +
> +               p0 = strchr(p, ')');
> +               if (p0 != NULL) {
> +                       size = p0 - p;
> +                       if (size >= RTE_NODE_NAMESIZE)
> +                               goto exit;
> +
> +                       if (num_patterns >= MAX_NODE_PATTERNS) {
> +                               printf("Too many nodes passed.\n");
> +                               goto exit;
> +                       }
> +
> +                       memcpy(node_pattern[num_patterns++], p, size);
> +               } else {
> +                       goto exit;
> +               }
> +       } else {
> +               goto exit;
> +       }
> +
> +       return 0;
> +exit:
> +       return ret;
> +}
> +
> +static void
> +set_default_node_pattern(void)
> +{
> +       uint16_t idx;
> +
> +       for (idx = 0; idx < test_node_list[0].size; idx++)
> +               strcpy(node_pattern[num_patterns++], test_node_list[0].nodes[idx]);
> +}
> +int
> +validate_node_names(uint64_t *valid_nodes)
> +{
> +       rte_node_t node_cnt = rte_node_max_count();
> +       bool pattern_matched = false;
> +       rte_node_t id = 0;
> +       int ret = -EINVAL;
> +       uint16_t idx, i, j;
> +
> +       for (idx = 0; idx < num_patterns; idx++) {
> +               for (id = 0; id < node_cnt; id++) {
> +                       if (strncmp(node_pattern[idx], rte_node_id_to_name(id),
> +                                   RTE_GRAPH_NAMESIZE) == 0)
> +                               break;
> +               }
> +               if (node_cnt == id) {
> +                       printf("Invalid node name passed\n");
> +                       return ret;
> +               }
> +       }
> +
> +       printf("num_ptrn:: %u\n", num_patterns);
> +       for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +               idx = 0;
> +               if (test_node_list[i].size == num_patterns) {
> +                       for (j = 0; j < num_patterns; j++) {
> +                               if (strncmp(node_pattern[j], test_node_list[i].nodes[j],
> +                                   RTE_GRAPH_NAMESIZE) == 0)
> +                                       idx++;
> +                       }
> +                       printf("idx::%u\n", idx);
> +                       if (idx == num_patterns)
> +                               pattern_matched = true;
> +               }
> +       }
> +
> +       if (!pattern_matched) {
> +               printf("Unsupported node pattern passed\n\n");
> +               printf("Test supported node patterns are:\n");
> +               for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +                       printf("(");
> +                       for (j = 0; j < (test_node_list[i].size - 1); j++)
> +                               printf("%s,", test_node_list[i].nodes[j]);
> +                       printf("%s", test_node_list[i].nodes[j]);
> +                       printf(")\n");
> +               }
> +
> +               return ret;
> +       }
> +
> +       for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> +               for (j = 0; j < num_patterns; j++) {
> +                       if (strncmp(node_pattern[j], supported_nodes[i].nodes[0],
> +                                   RTE_GRAPH_NAMESIZE) == 0) {
> +                               *valid_nodes |= supported_nodes[i].test_id;
> +                               break;
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +void
> +cleanup_node_pattern(void)
> +{
> +       while (num_patterns) {
> +               memset(node_pattern[num_patterns - 1], 0, RTE_GRAPH_NAMESIZE);
> +               num_patterns--;
> +       }
> +}
> +
> +static int
> +ethdev_tx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
> +{
> +       struct ethdev_tx_node_main *tx_node_data;
> +       struct rte_node_register *tx_node;
> +       char name[RTE_NODE_NAMESIZE];
> +       uint16_t port_id;
> +       uint32_t id;
> +       int i;
> +
> +       tx_node_data = ethdev_tx_node_data_get();
> +       tx_node = ethdev_tx_node_get();
> +
> +       for (i = 0; i < nb_confs; i++) {
> +               port_id = conf[i].port_id;
> +
> +               if (!rte_eth_dev_is_valid_port(port_id))
> +                       return -EINVAL;
> +
> +               /* Create a per port tx node from base node */
> +               snprintf(name, sizeof(name), "%u", port_id);
> +               id = rte_node_clone(tx_node->id, name);
> +               tx_node_data->nodes[port_id] = id;
> +
> +               printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node->name, name, id);
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +punt_kernel_node_configure(void)
> +{
> +       struct rte_node_register *punt_node;
> +       char name[RTE_NODE_NAMESIZE];
> +       struct lcore_conf *qconf;
> +       uint32_t lcore_id;
> +       uint32_t id;
> +
> +       punt_node = punt_kernel_node_get();
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +
> +               qconf = &lcore_conf[lcore_id];
> +
> +               /* Create a per lcore punt_kernel node from base node */
> +               snprintf(name, sizeof(name), "%u", lcore_id);
> +               id = rte_node_clone(punt_node->id, name);
> +               strcpy(qconf->punt_kernel_node_name, rte_node_id_to_name(id));
> +
> +               printf("punt_kernel node %s-%s: is at %u\n", punt_node->name, name, id);
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
> +{
> +       char name[RTE_NODE_NAMESIZE];
> +       uint16_t i, j, port_id;
> +       uint32_t id;
> +
> +       for (i = 0; i < nb_confs; i++) {
> +               port_id = conf[i].port_id;
> +
> +               if (!rte_eth_dev_is_valid_port(port_id))
> +                       return -EINVAL;
> +
> +               /* Create node for each rx port queue pair */
> +               for (j = 0; j < conf[i].num_rx_queues; j++) {
> +                       struct ethdev_rx_node_main *rx_node_data;
> +                       struct rte_node_register *rx_node;
> +                       ethdev_rx_node_elem_t *elem;
> +
> +                       rx_node_data = ethdev_rx_get_node_data_get();
> +                       rx_node = ethdev_rx_node_get();
> +                       snprintf(name, sizeof(name), "%u-%u", port_id, j);
> +                       /* Clone a new rx node with same edges as parent */
> +                       id = rte_node_clone(rx_node->id, name);
> +                       if (id == RTE_NODE_ID_INVALID)
> +                               return -EIO;
> +
> +                       /* Add it to list of ethdev rx nodes for lookup */
> +                       elem = malloc(sizeof(ethdev_rx_node_elem_t));
> +                       if (elem == NULL)
> +                               return -ENOMEM;
> +                       memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> +                       elem->ctx.port_id = port_id;
> +                       elem->ctx.queue_id = j;
> +                       elem->nid = id;
> +                       elem->next = rx_node_data->head;
> +                       rx_node_data->head = elem;
> +
> +                       printf("ethdev:: Rx node %s-%s: is at %u\n", rx_node->name, name, id);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> +{
> +       struct ethdev_rx_node_main *rx_node_data;
> +       ethdev_rx_node_elem_t *elem;
> +       char *next_nodes[16];
> +       rte_edge_t count;
> +       uint16_t i;
> +
> +       count = rte_node_edge_count(id);
> +       rte_node_edge_get(id, next_nodes);
> +
> +       for (i = 0; i < count; i++) {
> +               if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> +                       rx_node_data = ethdev_rx_get_node_data_get();
> +                       elem = rx_node_data->head;
> +                       while (elem->next != rx_node_data->head) {
> +                               if (elem->nid == id)
> +                                       break;
> +                               elem = elem->next;
> +                       }
> +
> +                       if (elem->nid == id)
> +                               elem->ctx.cls_next = i;
> +                       break;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_tx_node(struct rte_node_ethdev_config *conf, uint16_t nb_confs)
> +{
> +       const char * const pattern[] = {"ethdev_tx-*"};
> +       char name[RTE_NODE_NAMESIZE];
> +       const char *next_node = name;
> +       uint16_t i, j, port_id;
> +       uint32_t rx_id;
> +
> +       for (i = 0; i < nb_confs; i++) {
> +               port_id = conf[i].port_id;
> +
> +               if (!rte_eth_dev_is_valid_port(port_id))
> +                       return -EINVAL;
> +
> +               for (j = 0; j < conf[i].num_rx_queues; j++) {
> +
> +                       snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id, j);
> +                       rx_id = rte_node_from_name(name);
> +
> +                       /* Fill node pattern */
> +                       strcpy(node_pattern[num_patterns++], name);
> +
> +                       /* Prepare the actual name of the cloned node */
> +                       snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> +
> +                       /* Update ethdev_rx node edges */
> +                       rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
> +
> +                       /* Fill node pattern */
> +                       strcpy(node_pattern[num_patterns++], name);
> +
> +                       /* Update node_next details */
> +                       update_ethdev_rx_node_next(rx_id, pattern[0]);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_punt_kernel_node(void)
> +{
> +       uint16_t queueid, portid, queue;
> +       char name[RTE_NODE_NAMESIZE];
> +       const char *next_node = name;
> +       struct lcore_conf *qconf;
> +       uint32_t lcore_id;
> +       rte_node_t rx_id;
> +       const char * const pattern[] = {"punt_kernel-*"};
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +               qconf = &lcore_conf[lcore_id];
> +
> +               /* Init RX queues */
> +               for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +
> +                       portid = qconf->rx_queue_list[queue].port_id;
> +                       queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +                       snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid, queueid);
> +                       rx_id = rte_node_from_name(name);
> +
> +                       /* Fill node pattern */
> +                       strcpy(node_pattern[num_patterns++], name);
> +
> +                       next_node = qconf->punt_kernel_node_name;
> +
> +                       /* Fill node pattern */
> +                       strcpy(node_pattern[num_patterns++], next_node);
> +
> +                       /* Update ethdev_rx node edges */
> +                       rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);
> +
> +                       /* Update node_next details */
> +                       update_ethdev_rx_node_next(rx_id, pattern[0]);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +uint32_t
> +ethdev_ports_setup(void)
> +{
> +       struct rte_eth_dev_info dev_info;
> +       uint32_t nb_tx_queue, nb_lcores;
> +       uint32_t nb_ports, nb_conf = 0;
> +       uint8_t nb_rx_queue;
> +       uint16_t portid;
> +       int ret;
> +
> +       nb_ports = rte_eth_dev_count_avail();
> +       nb_lcores = rte_lcore_count();
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               struct rte_eth_conf local_port_conf = port_conf;
> +
> +               /* Skip ports that are not enabled */
> +               if ((enabled_port_mask & (1 << portid)) == 0) {
> +                       printf("\nSkipping disabled port %d\n", portid);
> +                       continue;
> +               }
> +
> +               /* Init port */
> +               printf("Initializing port %d ... ", portid);
> +               fflush(stdout);
> +
> +               nb_rx_queue = get_port_n_rx_queues(portid);
> +               nb_tx_queue = nb_lcores;
> +               if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> +                       nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> +               printf("Creating queues: nb_rxq=%d nb_txq=%u...\n", nb_rx_queue, nb_tx_queue);
> +
> +               rte_eth_dev_info_get(portid, &dev_info);
> +
> +               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 !=
> +                   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, 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_rx_queue, nb_tx_queue, &local_port_conf);
> +               if (ret < 0)
> +                       rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%d\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=%d\n",
> +                                ret, portid);
> +
> +               /* Init memory */
> +               if (!per_port_pool)
> +                       ret = init_mem(0, NB_MBUF(nb_ports));
> +               else
> +                       ret = init_mem(portid, NB_MBUF(1));
> +
> +               if (ret < 0)
> +                       rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> +
> +               /* Setup ethdev node config */
> +               ethdev_conf[nb_conf].port_id = portid;
> +               ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +               ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> +               if (!per_port_pool)
> +                       ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> +
> +               else
> +                       ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> +               ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> +
> +               nb_conf++;
> +       }
> +
> +       return nb_conf;
> +}
> +
> +void
> +ethdev_rxq_configure(void)
> +{
> +       struct rte_eth_dev_info dev_info;
> +       uint16_t queueid, portid;
> +       struct lcore_conf *qconf;
> +       uint8_t queue, socketid;
> +       uint32_t lcore_id;
> +       int ret;
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +               qconf = &lcore_conf[lcore_id];
> +               printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +               fflush(stdout);
> +
> +               /* Init RX queues */
> +               for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +                       struct rte_eth_rxconf rxq_conf;
> +
> +                       portid = qconf->rx_queue_list[queue].port_id;
> +                       queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +                       if (numa_on)
> +                               socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +                       else
> +                               socketid = 0;
> +
> +                       printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> +                       fflush(stdout);
> +
> +                       rte_eth_dev_info_get(portid, &dev_info);
> +                       rxq_conf = dev_info.default_rxconf;
> +                       rxq_conf.offloads = port_conf.rxmode.offloads;
> +                       if (!per_port_pool)
> +                               ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
> +                                                            &rxq_conf, pktmbuf_pool[0][socketid]);
> +                       else
> +                               ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,
> +                                                            &rxq_conf,
> +                                                            pktmbuf_pool[portid][socketid]);
> +                       if (ret < 0)
> +                               rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> +                                        ret, portid);
> +
> +                       snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
> +                                "ethdev_rx-%u-%u", portid, queueid);
> +               }
> +       }
> +       printf("\n");
> +}
> +
> +void
> +ethdev_txq_configure(void)
> +{
> +       struct rte_eth_dev_info dev_info;
> +       struct rte_eth_txconf *txconf;
> +       uint16_t queueid, portid;
> +       uint32_t lcore_id;
> +       uint8_t socketid;
> +       int ret;
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               struct rte_eth_conf local_port_conf = port_conf;
> +
> +               /* Skip ports that are not enabled */
> +               if ((enabled_port_mask & (1 << portid)) == 0) {
> +                       printf("\nSkipping disabled port %d\n", portid);
> +                       continue;
> +               }
> +
> +               rte_eth_dev_info_get(portid, &dev_info);
> +
> +               /* Init one TX queue per (lcore,port) pair */
> +               queueid = 0;
> +               for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +                       if (rte_lcore_is_enabled(lcore_id) == 0)
> +                               continue;
> +
> +                       if (numa_on)
> +                               socketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +                       else
> +                               socketid = 0;
> +
> +                       printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
> +                       fflush(stdout);
> +
> +                       txconf = &dev_info.default_txconf;
> +                       txconf->offloads = local_port_conf.txmode.offloads;
> +                       ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd, socketid, txconf);
> +                       if (ret < 0)
> +                               rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> +                                        ret, portid);
> +                       queueid++;
> +               }
> +       }
> +       printf("\n");
> +}
> +
> +int
> +configure_graph_nodes(uint64_t valid_nodes)
> +{
> +       int ret = 0;
> +
> +       if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> +               ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> +               if (ret) {
> +                       printf("ethdev_rx_node_configure: err=%d\n", ret);
> +                       goto exit;
> +               }
> +       }
> +
> +       if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> +               ret = ethdev_tx_node_configure(ethdev_conf, nb_conf);
> +               if (ret) {
> +                       printf("ethdev_tx_node_configure: err=%d\n", ret);
> +                       goto exit;
> +               }
> +       }
> +
> +       if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> +               ret = punt_kernel_node_configure();
> +               if (ret) {
> +                       printf("punt_kernel_node_configure: err=%d\n", ret);
> +                       goto exit;
> +               }
> +       }
> +
> +       cleanup_node_pattern();
> +
> +       if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_ETHDEV_RX_NODE)) {
> +               ret = link_ethdev_rx_to_tx_node(ethdev_conf, nb_conf);
> +               if (ret) {
> +                       printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> +                       goto exit;
> +               }
> +       } else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE | TEST_GRAPH_PUNT_KERNEL_NODE)) {
> +               link_ethdev_rx_to_punt_kernel_node();
> +       } else {
> +               printf("Invalid node map\n");
> +               ret = -EINVAL;
> +       }
> +
> +exit:
> +       return ret;
> +}
> +
> +void
> +start_eth_ports(void)
> +{
> +       uint16_t portid;
> +       int ret;
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               if ((enabled_port_mask & (1 << portid)) == 0)
> +                       continue;
> +
> +               ret = rte_eth_dev_start(portid);
> +               if (ret < 0)
> +                       rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", ret, portid);
> +
> +               if (promiscuous_on)
> +                       rte_eth_promiscuous_enable(portid);
> +       }
> +}
> +
> +void
> +stop_eth_ports(void)
> +{
> +       uint16_t portid;
> +       int ret;
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               if ((enabled_port_mask & (1 << portid)) == 0)
> +                       continue;
> +               printf("Closing port %d...", portid);
> +               ret = rte_eth_dev_stop(portid);
> +               if (ret != 0)
> +                       printf("Failed to stop port %u: %s\n", portid, rte_strerror(-ret));
> +               rte_eth_dev_close(portid);
> +       }
> +}
> +
> +int
> +create_graph(char pattern[][RTE_NODE_NAMESIZE], uint8_t num_patterns)
> +{
> +       struct rte_graph_param graph_conf;
> +       struct lcore_conf *qconf;
> +       uint32_t lcore_id;
> +
> +       node_patterns = malloc(MAX_NODE_PATTERNS * sizeof(*node_patterns));
> +       if (!node_patterns)
> +               return -ENOMEM;
> +
> +       memset(&graph_conf, 0, sizeof(graph_conf));
> +       graph_conf.node_patterns = node_patterns;
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               rte_graph_t graph_id;
> +               rte_edge_t i;
> +
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +
> +               qconf = &lcore_conf[lcore_id];
> +
> +               /* Skip graph creation if no source exists */
> +               if (!qconf->n_rx_queue)
> +                       continue;
> +
> +               for (i = 0; i < num_patterns; i++)
> +                       graph_conf.node_patterns[i] = pattern[i];
> +
> +               graph_conf.nb_node_patterns = num_patterns;
> +
> +               graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> +               snprintf(qconf->name, sizeof(qconf->name), "worker_%u", lcore_id);
> +
> +               graph_id = rte_graph_create(qconf->name, &graph_conf);
> +               if (graph_id == RTE_GRAPH_ID_INVALID)
> +                       rte_exit(EXIT_FAILURE, "rte_graph_create(): graph_id invalid for lcore%u\n",
> +                                lcore_id);
> +
> +               qconf->graph_id = graph_id;
> +               qconf->graph = rte_graph_lookup(qconf->name);
> +
> +               if (!qconf->graph)
> +                       rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph %s not found\n",
> +                                qconf->name);
> +       }
> +
> +       return 0;
> +}
> +
> +int
> +destroy_graph(void)
> +{
> +       uint32_t lcore_id;
> +       rte_graph_t id;
> +       int ret;
> +
> +       RTE_LCORE_FOREACH_WORKER(lcore_id)
> +       {
> +               if (lcore_conf[lcore_id].graph) {
> +                       id = rte_graph_from_name(lcore_conf[lcore_id].name);
> +                       if (rte_graph_destroy(id)) {
> +                               printf("graph_id %u destroy failed.\n", id);
> +                               ret = -1;
> +                       }
> +               }
> +       }
> +
> +       if (node_patterns)
> +               free(node_patterns);
> +
> +       return ret;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +       uint64_t valid_nodes;
> +       uint32_t lcore_id;
> +       int ret;
> +
> +       graph_walk_quit = false;
> +       force_quit = false;
> +       interactive = 0;
> +
> +       node_patterns = NULL;
> +
> +       signal(SIGINT, signal_handler);
> +       signal(SIGTERM, signal_handler);
> +
> +       testgraph_logtype = rte_log_register("testgraph");
> +       if (testgraph_logtype < 0)
> +               rte_exit(EXIT_FAILURE, "Cannot register log type");
> +
> +       set_default_node_pattern();
> +
> +       rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> +
> +       ret = rte_eal_init(argc, argv);
> +       if (ret < 0)
> +               rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n", rte_strerror(rte_errno));
> +       argc -= ret;
> +       argv += ret;
> +
> +       if (argc > 1) {
> +               ret = parse_cmdline_args(argc, argv);
> +               if (ret < 0)
> +                       rte_exit(EXIT_FAILURE, "Invalid command line parameters\n");
> +       }
> +
> +#ifdef RTE_LIB_CMDLINE
> +       if (init_cmdline() != 0)
> +               rte_exit(EXIT_FAILURE, "Could not initialise cmdline context.\n");
> +
> +       if (interactive == 1) {
> +               prompt();
> +       } else
> +#endif
> +       {
> +               if (validate_config() < 0)
> +                       rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> +
> +               ret = validate_node_names(&valid_nodes);
> +               if (ret)
> +                       rte_exit(EXIT_FAILURE, "validate_node_names: err=%d\n", ret);
> +
> +               nb_conf = ethdev_ports_setup();
> +
> +               ethdev_rxq_configure();
> +
> +               ethdev_txq_configure();
> +
> +               ret = configure_graph_nodes(valid_nodes);
> +               if (ret)
> +                       rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n", ret);
> +
> +               start_eth_ports();
> +
> +               check_all_ports_link_status(enabled_port_mask);
> +
> +               ret = create_graph(node_pattern, num_patterns);
> +               if (ret)
> +                       rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> +
> +               stats = create_graph_cluster_stats();
> +               if (stats == NULL)
> +                       rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
> +
> +               /* Launch per-lcore init on every worker lcore */
> +               rte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);
> +
> +               /* Accumulate and print stats on main until exit */
> +               if (rte_graph_has_stats_feature())
> +                       print_stats();
> +
> +               /* Wait for worker cores to exit */
> +               RTE_LCORE_FOREACH_WORKER(lcore_id) {
> +                       ret = rte_eal_wait_lcore(lcore_id);
> +                       if (ret < 0)
> +                               break;
> +               }
> +
> +               ret = destroy_graph();
> +
> +               stop_eth_ports();
> +       }
> +
> +       /* clean up the EAL */
> +       ret = rte_eal_cleanup();
> +       if (ret != 0)
> +               rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-ret));
> +
> +       return EXIT_SUCCESS;
> +}
> diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> new file mode 100644
> index 0000000000..b982d94781
> --- /dev/null
> +++ b/app/test-graph/testgraph.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _TESTGRAPH_H_
> +#define _TESTGRAPH_H_
> +
> +#include <stdbool.h>
> +
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +
> +
> +#define MAX_LCORE_PARAMS 1024
> +#define MAX_NODE_PATTERNS 128
> +
> +#ifndef BIT_ULL
> +#define BIT_ULL(nr) (1ULL << (nr))
> +#endif
> +
> +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> +
> +extern uint8_t cl_quit;
> +static volatile bool force_quit;
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern uint32_t enabled_port_mask;
> +extern uint32_t nb_conf;
> +
> +extern int promiscuous_on;        /**< Ports set in promiscuous mode off by default. */
> +extern uint8_t interactive;       /**< interactive mode is disabled by default. */
> +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> +extern int numa_on;               /**< NUMA is enabled by default. */
> +extern int per_port_pool;
> +
> +extern volatile bool graph_walk_quit;
> +extern volatile bool run_graph_walk;
> +extern struct rte_graph_cluster_stats *stats;
> +
> +extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> +extern uint8_t num_patterns;
> +
> +struct node_list {
> +       const char *nodes[MAX_NODE_PATTERNS];
> +       uint64_t test_id;
> +       uint8_t size;
> +};
> +
> +struct lcore_params {
> +       uint16_t port_id;
> +       uint8_t queue_id;
> +       uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +void prompt(void);
> +void prompt_exit(void);
> +int init_cmdline(void);
> +int validate_config(void);
> +int parse_cmdline_args(int argc, char **argv);
> +uint32_t ethdev_ports_setup(void);
> +void ethdev_rxq_configure(void);
> +void ethdev_txq_configure(void);
> +void start_eth_ports(void);
> +void stop_eth_ports(void);
> +int create_graph(char pattern[][RTE_NODE_NAMESIZE], uint8_t num_patterns);
> +int destroy_graph(void);
> +int graph_main_loop(void *conf);
> +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> +void check_all_ports_link_status(uint32_t port_mask);
> +int configure_graph_nodes(uint64_t valid_nodes);
> +
> +int parse_config(const char *q_arg);
> +int parse_node_patterns(const char *q_arg);
> +int validate_node_names(uint64_t *valid_nodes);
> +void cleanup_node_pattern(void);
> +
> +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> +       rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt, ##args)
> +
> +#endif /* _TESTGRAPH_H_ */
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index 6f84fc31ff..f18c508fa2 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -20,6 +20,7 @@ DPDK Tools User Guides
>      cryptoperf
>      comp_perf
>      testeventdev
> +    testgraph
>      testregex
>      testmldev
>      dts
> diff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst
> new file mode 100644
> index 0000000000..3c1e058724
> --- /dev/null
> +++ b/doc/guides/tools/testgraph.rst
> @@ -0,0 +1,131 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(C) 2023 Marvell International Ltd.
> +
> +dpdk-test-graph Application
> +===========================
> +
> +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK) application that allows
> +exercising various graph library features. This application has a generic framework to add
> +new test configurations and expand test coverage to verify the functionality of graph nodes
> +and observe the graph cluster statistics.
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options:
> +
> +.. code-block:: console
> +
> +   dpdk-test-eventdev [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +The following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-test-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> +        list of cores to use.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +The following are the application command-line options:
> +
> +* ``-p <n>``
> +
> +        Set the ethdev port mask.
> +
> +* ``-P``
> +
> +        Set the ethdev ports in promiscuous mode.
> +
> +* ``--config <config>``
> +
> +        Set the Rxq configuration.
> +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> +
> +* ``--node-pattern <n>``
> +
> +        Set the node patterns to use in graph creation.
> +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> +
> +* ``--per-port-pool``
> +
> +        Use separate buffer pool per port.
> +
> +* ``--no-numa``
> +
> +        Disable numa awareness.
> +
> +* ``--interactive``
> +
> +        Switch to interactive mode.
> +
> +Running the Tool
> +~~~~~~~~~~~~~~~~
> +
> +Here is the sample command line to run simple iofwd test::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> +
> +Below is a sample command line to punt rx packets to kernel::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> +
> +Interactive mode
> +~~~~~~~~~~~~~~~~
> +
> +Tool uses ``--interactive`` command line option to enter interactive mode and use cmdline options
> +to setup the required node configurations, create graph and than start graph_walk.
> +
> +
> +testgraph> help
> +
> +Help is available for the following sections:
> +
> +    help control                    : Start and stop graph walk.
> +    help display                    : Displaying port, stats and config information.
> +    help config                     : Configuration information.
> +    help all                        : All of the above sections.
> +
> +testgraph> help all
> +
> +Control forwarding:
> +
> +start graph_walk
> + Start graph_walk on worker threads.
> +
> +stop graph_walk
> + Stop worker threads from running graph_walk.
> +
> +quit
> + Quit to prompt.
> +
> +
> +Display:
> +
> +show node_list
> + Display the list of supported nodes.
> +
> +show graph_stats
> + Display the node statistics of graph cluster.
> +
> +
> +Configuration:
> +
> +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> + Set lcore configuration.
> +
> +create_graph (node0_name,node1_name,...,nodeX_name)
> + Create graph instances using the provided node details.
> +
> +destroy_graph
> + Destroy the graph instances.
> +
> +testgraph>
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v2 4/4] app: add testgraph application
  2023-05-09  3:39       ` Vamsi Krishna Attunuru
@ 2023-05-09  8:53         ` Sunil Kumar Kori
  2023-05-09 12:24           ` Vamsi Krishna Attunuru
  0 siblings, 1 reply; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-05-09  8:53 UTC (permalink / raw)
  To: Vamsi Krishna Attunuru, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Nithin Kumar Dabilpuram

> -----Original Message-----
> From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Sent: Tuesday, May 9, 2023 9:10 AM
> To: Sunil Kumar Kori <skori@marvell.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>
> Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> 
> 
> 
> > -----Original Message-----
> > From: Sunil Kumar Kori <skori@marvell.com>
> > Sent: Tuesday, May 9, 2023 8:04 AM
> > To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> > Dabilpuram <ndabilpuram@marvell.com>
> > Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> >
> > Is there any user guide similar to testpmd ?
> >
>    Please refer doc/guides/tools/testgraph.rst for details.
> 
Thanks. 
> > > -----Original Message-----
> > > From: Vamsi Attunuru <vattunuru@marvell.com>
> > > Sent: Tuesday, April 25, 2023 6:45 PM
> > > To: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> > > <jerinj@marvell.com>
> > > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> > > Dabilpuram <ndabilpuram@marvell.com>
> > > Subject: [EXT] [PATCH v2 4/4] app: add testgraph application
> > >
> > > External Email
> > >
> > > ----------------------------------------------------------------------
> > > Patch adds test-graph application to validate graph
> > > and node libraries.
> > >
> > > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > > ---
> > >  app/meson.build                |    1 +
> > >  app/test-graph/cmdline.c       |  211 +++++
> > >  app/test-graph/cmdline_graph.c |  294 +++++++
> > >  app/test-graph/cmdline_graph.h |   19 +
> > >  app/test-graph/meson.build     |   14 +
> > >  app/test-graph/parameters.c    |  157 ++++
> > >  app/test-graph/testgraph.c     | 1426
> > ++++++++++++++++++++++++++++++++
> > >  app/test-graph/testgraph.h     |   91 ++
> > >  doc/guides/tools/index.rst     |    1 +
> > >  doc/guides/tools/testgraph.rst |  131 +++
> > >  10 files changed, 2345 insertions(+)
> > >

[Code Snipped]

> > > +++ b/doc/guides/tools/testgraph.rst
> > > @@ -0,0 +1,131 @@
> > > +..  SPDX-License-Identifier: BSD-3-Clause
> > > +    Copyright(C) 2023 Marvell International Ltd.
> > > +
> > > +dpdk-test-graph Application
> > > +===========================
> > > +
> > > +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK)
> > > application that allows
> > > +exercising various graph library features. This application has a generic
> > > framework to add
> > > +new test configurations and expand test coverage to verify the
> > functionality
> > > of graph nodes
> > > +and observe the graph cluster statistics.
> > > +
> > > +Running the Application
> > > +-----------------------
> > > +
> > > +The application has a number of command line options:
> > > +
> > > +.. code-block:: console
> > > +
> > > +   dpdk-test-eventdev [EAL Options] -- [application options]
> > > +
> > > +EAL Options
> > > +~~~~~~~~~~~
> > > +
> > > +The following are the EAL command-line options that can be used in
> > > conjunction
> > > +with the ``dpdk-test-graph`` application.
> > > +See the DPDK Getting Started Guides for more information on these
> > > options.
> > > +
> > > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > > +
> > > +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> > > +        list of cores to use.
> > > +
> > > +Application Options
> > > +~~~~~~~~~~~~~~~~~~~
> > > +
> > > +The following are the application command-line options:
> > > +
> > > +* ``-p <n>``
> > > +
> > > +        Set the ethdev port mask.
> > > +
> > > +* ``-P``
> > > +
> > > +        Set the ethdev ports in promiscuous mode.
> > > +
> > > +* ``--config <config>``
> > > +
> > > +        Set the Rxq configuration.
> > > +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> > > +
> > > +* ``--node-pattern <n>``
> > > +
> > > +        Set the node patterns to use in graph creation.
> > > +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).

It looks like this option is used to create a chain of nodes. Is my understanding correct ?

If yes, then how can we create a node having two or more edges. 
Like in l3fwd-graph application, cls_node is further connected to pkt-drop and ip4_lookup. 
Packet can move to respective nodes based on runtime decision.

If not, then how above option should be used for the same ?
> > > +
> > > +* ``--per-port-pool``
> > > +
> > > +        Use separate buffer pool per port.
> > > +
> > > +* ``--no-numa``
> > > +
> > > +        Disable numa awareness.
> > > +
> > > +* ``--interactive``
> > > +
> > > +        Switch to interactive mode.
> > > +
> > > +Running the Tool
> > > +~~~~~~~~~~~~~~~~
> > > +
> > > +Here is the sample command line to run simple iofwd test::
> > > +
> > > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P
> \
> > > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> > > +
> > > +Below is a sample command line to punt rx packets to kernel::
> > > +
> > > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P
> \
> > > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> > > +
> > > +Interactive mode
> > > +~~~~~~~~~~~~~~~~
> > > +
> > > +Tool uses ``--interactive`` command line option to enter interactive mode
> > > and use cmdline options
> > > +to setup the required node configurations, create graph and than start
> > > graph_walk.
> > > +
> > > +
> > > +testgraph> help
> > > +
> > > +Help is available for the following sections:
> > > +
> > > +    help control                    : Start and stop graph walk.
> > > +    help display                    : Displaying port, stats and config information.
> > > +    help config                     : Configuration information.
> > > +    help all                        : All of the above sections.
> > > +
> > > +testgraph> help all
> > > +
> > > +Control forwarding:
> > > +
> > > +start graph_walk
> > > + Start graph_walk on worker threads.
> > > +
> > > +stop graph_walk
> > > + Stop worker threads from running graph_walk.
> > > +
> > > +quit
> > > + Quit to prompt.
> > > +
> > > +
> > > +Display:
> > > +
> > > +show node_list
> > > + Display the list of supported nodes.
> > > +
> > > +show graph_stats
> > > + Display the node statistics of graph cluster.
> > > +
> > > +
> > > +Configuration:
> > > +
> > > +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> > > + Set lcore configuration.
> > > +
> > > +create_graph (node0_name,node1_name,...,nodeX_name)
> > > + Create graph instances using the provided node details.
> > > +
> > > +destroy_graph
> > > + Destroy the graph instances.
> > > +
> > > +testgraph>
> > > --
> > > 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v2 4/4] app: add testgraph application
  2023-05-09  8:53         ` Sunil Kumar Kori
@ 2023-05-09 12:24           ` Vamsi Krishna Attunuru
  0 siblings, 0 replies; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-05-09 12:24 UTC (permalink / raw)
  To: Sunil Kumar Kori, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Nithin Kumar Dabilpuram



> -----Original Message-----
> From: Sunil Kumar Kori <skori@marvell.com>
> Sent: Tuesday, May 9, 2023 2:24 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>
> Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> 
> > -----Original Message-----
> > From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > Sent: Tuesday, May 9, 2023 9:10 AM
> > To: Sunil Kumar Kori <skori@marvell.com>; dev@dpdk.org;
> > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> > Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>
> > Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> >
> >
> >
> > > -----Original Message-----
> > > From: Sunil Kumar Kori <skori@marvell.com>
> > > Sent: Tuesday, May 9, 2023 8:04 AM
> > > To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> > > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> > > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> > > Dabilpuram <ndabilpuram@marvell.com>
> > > Subject: RE: [EXT] [PATCH v2 4/4] app: add testgraph application
> > >
> > > Is there any user guide similar to testpmd ?
> > >
> >    Please refer doc/guides/tools/testgraph.rst for details.
> >
> Thanks.
> > > > -----Original Message-----
> > > > From: Vamsi Attunuru <vattunuru@marvell.com>
> > > > Sent: Tuesday, April 25, 2023 6:45 PM
> > > > To: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> > > > <jerinj@marvell.com>
> > > > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> > > > Dabilpuram <ndabilpuram@marvell.com>
> > > > Subject: [EXT] [PATCH v2 4/4] app: add testgraph application
> > > >
> > > > External Email
> > > >
> > > > ------------------------------------------------------------------
> > > > ---- Patch adds test-graph application to validate graph and node
> > > > libraries.
> > > >
> > > > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > > > ---
> > > >  app/meson.build                |    1 +
> > > >  app/test-graph/cmdline.c       |  211 +++++
> > > >  app/test-graph/cmdline_graph.c |  294 +++++++
> > > >  app/test-graph/cmdline_graph.h |   19 +
> > > >  app/test-graph/meson.build     |   14 +
> > > >  app/test-graph/parameters.c    |  157 ++++
> > > >  app/test-graph/testgraph.c     | 1426
> > > ++++++++++++++++++++++++++++++++
> > > >  app/test-graph/testgraph.h     |   91 ++
> > > >  doc/guides/tools/index.rst     |    1 +
> > > >  doc/guides/tools/testgraph.rst |  131 +++
> > > >  10 files changed, 2345 insertions(+)
> > > >
> 
> [Code Snipped]
> 
> > > > +++ b/doc/guides/tools/testgraph.rst
> > > > @@ -0,0 +1,131 @@
> > > > +..  SPDX-License-Identifier: BSD-3-Clause
> > > > +    Copyright(C) 2023 Marvell International Ltd.
> > > > +
> > > > +dpdk-test-graph Application
> > > > +===========================
> > > > +
> > > > +The ``dpdk-test-graph`` tool is a Data Plane Development Kit
> > > > +(DPDK)
> > > > application that allows
> > > > +exercising various graph library features. This application has a
> > > > +generic
> > > > framework to add
> > > > +new test configurations and expand test coverage to verify the
> > > functionality
> > > > of graph nodes
> > > > +and observe the graph cluster statistics.
> > > > +
> > > > +Running the Application
> > > > +-----------------------
> > > > +
> > > > +The application has a number of command line options:
> > > > +
> > > > +.. code-block:: console
> > > > +
> > > > +   dpdk-test-eventdev [EAL Options] -- [application options]
> > > > +
> > > > +EAL Options
> > > > +~~~~~~~~~~~
> > > > +
> > > > +The following are the EAL command-line options that can be used
> > > > +in
> > > > conjunction
> > > > +with the ``dpdk-test-graph`` application.
> > > > +See the DPDK Getting Started Guides for more information on these
> > > > options.
> > > > +
> > > > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > > > +
> > > > +        Set the hexadecimal bitmask of the cores to run on. The corelist is
> a
> > > > +        list of cores to use.
> > > > +
> > > > +Application Options
> > > > +~~~~~~~~~~~~~~~~~~~
> > > > +
> > > > +The following are the application command-line options:
> > > > +
> > > > +* ``-p <n>``
> > > > +
> > > > +        Set the ethdev port mask.
> > > > +
> > > > +* ``-P``
> > > > +
> > > > +        Set the ethdev ports in promiscuous mode.
> > > > +
> > > > +* ``--config <config>``
> > > > +
> > > > +        Set the Rxq configuration.
> > > > +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> > > > +
> > > > +* ``--node-pattern <n>``
> > > > +
> > > > +        Set the node patterns to use in graph creation.
> > > > +        (i.e. ``--node-pattern
> (node_name0,node_name1[,node_nameX])``).
> 
> It looks like this option is used to create a chain of nodes. Is my
> understanding correct ?
> 
     Yes

> If yes, then how can we create a node having two or more edges.
> Like in l3fwd-graph application, cls_node is further connected to pkt-drop
> and ip4_lookup.
> Packet can move to respective nodes based on runtime decision.
> 
> If not, then how above option should be used for the same ?

--node-pattern option mainly provides the next node details(which test can configure in node_next details). To link other next nodes to the graph, test can be extended to add those edges implicitly based on the node list used for graph creation, or as suggested the same option can be used for the same.

> > > > +
> > > > +* ``--per-port-pool``
> > > > +
> > > > +        Use separate buffer pool per port.
> > > > +
> > > > +* ``--no-numa``
> > > > +
> > > > +        Disable numa awareness.
> > > > +
> > > > +* ``--interactive``
> > > > +
> > > > +        Switch to interactive mode.
> > > > +
> > > > +Running the Tool
> > > > +~~~~~~~~~~~~~~~~
> > > > +
> > > > +Here is the sample command line to run simple iofwd test::
> > > > +
> > > > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF
> > > > + -- -p 0x3 -P
> > \
> > > > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> > > > +
> > > > +Below is a sample command line to punt rx packets to kernel::
> > > > +
> > > > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF
> > > > + -- -p 0x3 -P
> > \
> > > > +       --config "(0,0,2),(1,0,2)" --node-pattern
> "(ethdev_rx,punt_kernel)"
> > > > +
> > > > +Interactive mode
> > > > +~~~~~~~~~~~~~~~~
> > > > +
> > > > +Tool uses ``--interactive`` command line option to enter
> > > > +interactive mode
> > > > and use cmdline options
> > > > +to setup the required node configurations, create graph and than
> > > > +start
> > > > graph_walk.
> > > > +
> > > > +
> > > > +testgraph> help
> > > > +
> > > > +Help is available for the following sections:
> > > > +
> > > > +    help control                    : Start and stop graph walk.
> > > > +    help display                    : Displaying port, stats and config information.
> > > > +    help config                     : Configuration information.
> > > > +    help all                        : All of the above sections.
> > > > +
> > > > +testgraph> help all
> > > > +
> > > > +Control forwarding:
> > > > +
> > > > +start graph_walk
> > > > + Start graph_walk on worker threads.
> > > > +
> > > > +stop graph_walk
> > > > + Stop worker threads from running graph_walk.
> > > > +
> > > > +quit
> > > > + Quit to prompt.
> > > > +
> > > > +
> > > > +Display:
> > > > +
> > > > +show node_list
> > > > + Display the list of supported nodes.
> > > > +
> > > > +show graph_stats
> > > > + Display the node statistics of graph cluster.
> > > > +
> > > > +
> > > > +Configuration:
> > > > +
> > > > +set lcore_config
> > > > +(port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> > > > + Set lcore configuration.
> > > > +
> > > > +create_graph (node0_name,node1_name,...,nodeX_name)
> > > > + Create graph instances using the provided node details.
> > > > +
> > > > +destroy_graph
> > > > + Destroy the graph instances.
> > > > +
> > > > +testgraph>
> > > > --
> > > > 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [PATCH v2 4/4] app: add testgraph application
  2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
  2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
@ 2023-05-12 11:08     ` Yan, Zhirun
  2023-05-22  7:07       ` Vamsi Krishna Attunuru
  2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
  2 siblings, 1 reply; 182+ messages in thread
From: Yan, Zhirun @ 2023-05-12 11:08 UTC (permalink / raw)
  To: Vamsi Attunuru, dev, thomas, jerinj
  Cc: ndabilpuram, Liang, Cunming, Wang, Haiyue, Sunil Kumar Kori



> -----Original Message-----
> From: Vamsi Attunuru <vattunuru@marvell.com>
> Sent: Tuesday, April 25, 2023 9:15 PM
> To: dev@dpdk.org; thomas@monjalon.net; jerinj@marvell.com
> Cc: vattunuru@marvell.com; ndabilpuram@marvell.com
> Subject: [PATCH v2 4/4] app: add testgraph application
> 
> Patch adds test-graph application to validate graph
> and node libraries.
> 
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  app/meson.build                |    1 +
>  app/test-graph/cmdline.c       |  211 +++++
>  app/test-graph/cmdline_graph.c |  294 +++++++
>  app/test-graph/cmdline_graph.h |   19 +
>  app/test-graph/meson.build     |   14 +
>  app/test-graph/parameters.c    |  157 ++++
>  app/test-graph/testgraph.c     | 1426 ++++++++++++++++++++++++++++++++
>  app/test-graph/testgraph.h     |   91 ++
>  doc/guides/tools/index.rst     |    1 +
>  doc/guides/tools/testgraph.rst |  131 +++
>  10 files changed, 2345 insertions(+)
> 
> diff --git a/app/meson.build b/app/meson.build
> index 74d2420f67..6c7b24e604 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -22,6 +22,7 @@ apps = [
>          'test-eventdev',
>          'test-fib',
>          'test-flow-perf',
> +        'test-graph',
>          'test-gpudev',
>          'test-mldev',
>          'test-pipeline',
> diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> new file mode 100644
> index 0000000000..d9474d827a
> --- /dev/null
> +++ b/app/test-graph/cmdline.c
> @@ -0,0 +1,211 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <stdlib.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_etheraddr.h>
> +#include <cmdline_parse_ipaddr.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_rdline.h>
> +#include <cmdline_socket.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +static struct cmdline *testgraph_cl;
> +static cmdline_parse_ctx_t *main_ctx;
> +
> +/* *** Help command with introduction. *** */
> +struct cmd_help_brief_result {
> +	cmdline_fixed_string_t help;
> +};
> +
> +static void
> +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> __rte_unused void *data)
> +{
> +	cmdline_printf(cl,
> +		       "\n"
> +		       "Help is available for the following sections:\n\n"
> +		       "    help control                    : Start and stop graph walk.\n"
> +		       "    help display                    : Displaying port, stats and config
> "
> +		       "information.\n"
> +		       "    help config                     : Configuration information.\n"
> +		       "    help all                        : All of the above sections.\n\n");
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_brief_help =
> +	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help, "help");
> +
> +static cmdline_parse_inst_t cmd_help_brief = {
> +	.f = cmd_help_brief_parsed,
> +	.data = NULL,
> +	.help_str = "help: Show help",
> +	.tokens = {
> +			(void *)&cmd_help_brief_help,
> +			NULL,
> +		},
> +};
> +
> +/* *** Help command with help sections. *** */
> +struct cmd_help_long_result {
> +	cmdline_fixed_string_t help;
> +	cmdline_fixed_string_t section;
> +};
> +
> +static void
> +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl, __rte_unused
> void *data)
> +{
> +	int show_all = 0;
> +	struct cmd_help_long_result *res = parsed_result;
> +
> +	if (!strcmp(res->section, "all"))
> +		show_all = 1;
> +
> +	if (show_all || !strcmp(res->section, "control")) {
> +
> +		cmdline_printf(cl, "\n"
> +				   "Control forwarding:\n"
> +				   "-------------------\n\n"
> +
> +				   "start graph_walk\n"
> +				   " Start graph_walk on worker threads.\n\n"
> +
> +				   "stop graph_walk\n"
> +				   " Stop worker threads from running
> graph_walk.\n\n"
> +
> +				   "quit\n"
> +				   "    Quit to prompt.\n\n");
> +	}
> +
> +	if (show_all || !strcmp(res->section, "display")) {
> +
> +		cmdline_printf(cl,
> +			       "\n"
> +			       "Display:\n"
> +			       "--------\n\n"
> +
> +			       "show node_list\n"
> +			       " Display the list of supported nodes.\n\n"
> +
> +			       "show graph_stats\n"
> +			       " Display the node statistics of graph cluster.\n\n");
> +	}
> +
> +	if (show_all || !strcmp(res->section, "config")) {
> +		cmdline_printf(cl, "\n"
> +				   "Configuration:\n"
> +				   "--------------\n"
> +				   "set lcore_config
> (port_id0,rxq0,lcore_idX),..."
> +				   ".....,(port_idX,rxqX,lcoreidY)\n"
> +				   " Set lcore configuration.\n\n"
> +
> +				   "create_graph
> (node0_name,node1_name,...,nodeX_name)\n"
> +				   " Create graph instances using the provided
> node details.\n\n"
> +
> +				   "destroy_graph\n"
> +				   " Destroy the graph instances.\n\n");
> +	}
> +}
> +
> +static cmdline_parse_token_string_t cmd_help_long_help =
> +	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help, "help");
> +
> +static cmdline_parse_token_string_t cmd_help_long_section =
> TOKEN_STRING_INITIALIZER(
> +	struct cmd_help_long_result, section, "all#control#display#config");
> +
> +static cmdline_parse_inst_t cmd_help_long = {
> +	.f = cmd_help_long_parsed,
> +	.data = NULL,
> +	.help_str = "help all|control|display|config: "
> +		    "Show help",
> +	.tokens = {
> +			(void *)&cmd_help_long_help,
> +			(void *)&cmd_help_long_section,
> +			NULL,
> +		},
> +};
> +
> +/* *** QUIT *** */
> +struct cmd_quit_result {
> +	cmdline_fixed_string_t quit;
> +};
> +
> +static void
> +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> __rte_unused void *data)
> +{
> +	cmdline_quit(cl);
> +}
> +
> +static cmdline_parse_token_string_t cmd_quit_quit =
> +	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> +
> +static cmdline_parse_inst_t cmd_quit = {
> +	.f = cmd_quit_parsed,
> +	.data = NULL,
> +	.help_str = "quit: Exit application",
> +	.tokens = {
> +			(void *)&cmd_quit_quit,
> +			NULL,
> +		},
> +};
> +
> +/* list of instructions */
> +static cmdline_parse_ctx_t builtin_ctx[] = {
> +	(cmdline_parse_inst_t *)&cmd_help_brief,
> +	(cmdline_parse_inst_t *)&cmd_help_long,
> +	(cmdline_parse_inst_t *)&cmd_quit,
> +	(cmdline_parse_inst_t *)&cmd_show_node_list,
> +	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
> +	(cmdline_parse_inst_t *)&cmd_create_graph,
> +	(cmdline_parse_inst_t *)&cmd_destroy_graph,
> +	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
> +	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> +	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
> +	NULL,
> +};
> +
> +int
> +init_cmdline(void)
> +{
> +	unsigned int count;
> +	unsigned int i;
> +
> +	count = 0;
> +	for (i = 0; builtin_ctx[i] != NULL; i++)
> +		count++;
> +
> +	/* cmdline expects a NULL terminated array */
> +	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> +	if (main_ctx == NULL)
> +		return -1;
> +
> +	count = 0;
> +	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> +		main_ctx[count] = builtin_ctx[i];
> +
> +	return 0;
> +}
> +
> +void
> +prompt_exit(void)
> +{
> +	cmdline_quit(testgraph_cl);
> +}
> +
> +/* prompt function, called from main on MAIN lcore */
> +void
> +prompt(void)
> +{
> +	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> +	if (testgraph_cl == NULL) {
> +		fprintf(stderr, "Failed to create stdin based cmdline context\n");
> +		return;
> +	}
> +
> +	cmdline_interact(testgraph_cl);
> +	cmdline_stdin_exit(testgraph_cl);
> +}
> diff --git a/app/test-graph/cmdline_graph.c b/app/test-graph/cmdline_graph.c
> new file mode 100644
> index 0000000000..d66149a224
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.c
> @@ -0,0 +1,294 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +
> +#include "cmdline_graph.h"
> +#include "testgraph.h"
> +
> +/* *** Show supported node details *** */
> +struct cmd_show_node_list_result {
> +	cmdline_fixed_string_t show;
> +	cmdline_fixed_string_t node_list;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_node_list_show =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, show,
> "show");
> +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> node_list, "node_list");
> +
> +static void
> +cmd_show_node_list_parsed(__rte_unused void *parsed_result, __rte_unused
> struct cmdline *cl,
> +			  __rte_unused void *data)
> +{
> +	rte_node_t node_cnt = rte_node_max_count();
> +	rte_node_t id;
> +
> +	printf("\n**** Supported Graph Nodes ****\n");
> +	for (id = 0; id < node_cnt; id++)
> +		printf("%s\n", rte_node_id_to_name(id));
> +
> +	printf("********************************\n");
> +}
> +
> +cmdline_parse_inst_t cmd_show_node_list = {
> +	.f = cmd_show_node_list_parsed,
> +	.data = NULL,
> +	.help_str = "show node_list",
> +	.tokens = {
> +			(void *)&cmd_show_node_list_show,
> +			(void *)&cmd_show_node_list_node_list,
> +			NULL,
> +		},
> +};
> +
> +/* *** Set lcore config *** */
> +struct cmd_set_lcore_config_result {
> +	cmdline_fixed_string_t set;
> +	cmdline_fixed_string_t lcore_config;
> +	cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set,
> "set");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =
> +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> lcore_config, "lcore_config");
> +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string =
> TOKEN_STRING_INITIALIZER(
> +	struct cmd_set_lcore_config_result, token_string,
> TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct
> cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	struct cmd_set_lcore_config_result *res = parsed_result;
> +	const char *t_str = res->token_string;
> +	int ret;
> +
> +	/* Parse string */
> +	ret = parse_config(t_str);
> +	if (ret) {
> +		printf(" lcore_config string parse error\n");
> +		return;
> +	}
> +
> +	validate_config();
> +}
> +
> +cmdline_parse_inst_t cmd_set_lcore_config = {
> +	.f = cmd_set_lcore_config_parsed,
> +	.data = NULL,
> +	.help_str = "set lcore_config "
> +		    "(port,queue,lcore),[(port,queue,lcore) ...
> (port,queue,lcore)]",
> +	.tokens = {
> +			(void *)&cmd_set_lcore_config_set,
> +			(void *)&cmd_set_lcore_config_lcore_config,
> +			(void *)&cmd_set_lcore_config_token_string,
> +			NULL,
> +		},
> +};
> +
> +/* *** Create graph *** */
> +struct cmd_create_graph_result {
> +	cmdline_fixed_string_t create_graph;
> +	cmdline_multi_string_t token_string;
> +};
> +
> +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> create_graph, "create_graph");
> +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> token_string, TOKEN_STRING_MULTI);
> +
> +static void
> +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct cmdline
> *cl,
> +			__rte_unused void *data)
> +{
> +	struct cmd_create_graph_result *res = parsed_result;
> +	const char *t_str = res->token_string;
> +	uint64_t valid_nodes = 0;
> +	int ret;
> +
> +	ret = parse_node_patterns(t_str);
> +	if (ret) {
> +		printf("parse_node_patterns failed\n");
> +		cleanup_node_pattern();
> +		return;
> +	}
> +
> +	ret = validate_node_names(&valid_nodes);
> +	if (ret) {
> +		printf("validate_node_names() failed\n");
> +		cleanup_node_pattern();
> +		return;
> +	}

Hi Vamsi,

The global node_pattern should be cleaned in destroy stage or make sure it populated from cmd line completely.
In Interactive-mode, user may create and destroy a graph and then create a new one.
For this case, the node name is changed by the previous creation, especially for rx/tx node.(ethdev_rx -> ethdev_rx-0-0)

> +
> +	nb_conf = ethdev_ports_setup();
> +
> +	ethdev_rxq_configure();
> +	ethdev_txq_configure();
> +
> +	ret = configure_graph_nodes(valid_nodes);
> +	if (ret)
> +		rte_exit(EXIT_FAILURE, "configure_graph_nodes: err=%d\n",
> ret);
> +
> +	ret = create_graph(valid_nodes);
> +	if (ret)
> +		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> +
> +	stats = create_graph_cluster_stats();
> +	if (stats == NULL)
> +		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats() failed\n");
> +
> +	check_all_ports_link_status(enabled_port_mask);
> +	start_eth_ports();
> +}

Could this create cmd be break down for graph_config and graph_create?
In graph_config stage, we can set/verify graph topo, chose graph-walk models and update node ctx, etc.

> +
> +cmdline_parse_inst_t cmd_create_graph = {
> +	.f = cmd_create_graph_parsed,
> +	.data = NULL,
> +	.help_str = "create_graph "
> +		    "[node_name0,node_name1,node_name2 ... node_nameX]",
> +	.tokens = {
> +			(void *)&cmd_create_graph_create_graph,
> +			(void *)&cmd_create_graph_token_string,
> +			NULL,
> +		},
> +};
> +
> +/**** Destroy graph ****/
> +struct cmd_destroy_graph_result {
> +	cmdline_fixed_string_t destroy_graph;
> +};
> +
> +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =
> +	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result,
> destroy_graph, "destroy_graph");
> +
> +static void
> +cmd_destroy_graph_parsed(__rte_unused void *parsed_result, __rte_unused
> struct cmdline *cl,
> +			 __rte_unused void *data)
> +{
> +	uint32_t lcore_id;
> +
> +	run_graph_walk = false;
> +	graph_walk_quit = true;
> +
> +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> +		rte_eal_wait_lcore(lcore_id);
> +
> +	destroy_graph();
> +	stop_eth_ports();
> +}
> +
> +cmdline_parse_inst_t cmd_destroy_graph = {
> +	.f = cmd_destroy_graph_parsed,
> +	.data = NULL,
> +	.help_str = "destroy_graph",
> +	.tokens = {
> +			(void *)&cmd_destroy_graph_destroy_graph,
> +			NULL,
> +		},
> +};
> +
> +/**** Start graph_walk ****/
> +struct cmd_start_graph_walk_result {
> +	cmdline_fixed_string_t start;
> +	cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, start,
> "start");
> +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =
> +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> graph_walk, "graph_walk");
> +
> +static void
> +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	static bool launch_graph_walk;
> +
> +	if (!launch_graph_walk) {
> +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> SKIP_MAIN);

As run_graph_walk is used to control graph-walk, Why not put the launch in create stage?

> +		launch_graph_walk = true;
> +	}
> +
> +	run_graph_walk = true;
> +}
> +
> +cmdline_parse_inst_t cmd_start_graph_walk = {
> +	.f = cmd_start_graph_walk_parsed,
> +	.data = NULL,
> +	.help_str = "start graph_walk",
> +	.tokens = {
> +			(void *)&cmd_start_graph_walk_start,
> +			(void *)&cmd_start_graph_walk_graph_walk,
> +			NULL,
> +		},
> +};
> +
> +/**** Stop graph_walk ****/
> +struct cmd_stop_graph_walk_result {
> +	cmdline_fixed_string_t stop;
> +	cmdline_fixed_string_t graph_walk;
> +};
> +
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, stop,
> "stop");
> +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =
> +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> graph_walk, "graph_walk");
> +
> +static void
> +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			   __rte_unused void *data)
> +{
> +	run_graph_walk = false;
> +}
> +
> +cmdline_parse_inst_t cmd_stop_graph_walk = {
> +	.f = cmd_stop_graph_walk_parsed,
> +	.data = NULL,
> +	.help_str = "stop graph_walk",
> +	.tokens = {
> +			(void *)&cmd_stop_graph_walk_stop,
> +			(void *)&cmd_stop_graph_walk_graph_walk,
> +			NULL,
> +		},
> +};
> +
> +/**** Show graph_stats ****/
> +struct cmd_show_graph_stats_result {
> +	cmdline_fixed_string_t show;
> +	cmdline_fixed_string_t graph_stats;
> +};
> +
> +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> show, "show");
> +static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =
> +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> graph_stats, "graph_stats");
> +
> +static void
> +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result,
> __rte_unused struct cmdline *cl,
> +			    __rte_unused void *data)
> +{
> +	if (rte_graph_has_stats_feature()) {
> +		if (stats)
> +			rte_graph_cluster_stats_get(stats, 0);
> +	} else {
> +		printf(" graph stats feature not enabled in rte_config.\n");
> +	}
> +}
> +
> +cmdline_parse_inst_t cmd_show_graph_stats = {
> +	.f = cmd_show_graph_stats_parsed,
> +	.data = NULL,
> +	.help_str = "show graph_stats",
> +	.tokens = {
> +			(void *)&cmd_show_graph_stats_show,
> +			(void *)&cmd_show_graph_stats_graph_stats,
> +			NULL,
> +		},
> +};
> diff --git a/app/test-graph/cmdline_graph.h b/app/test-graph/cmdline_graph.h
> new file mode 100644
> index 0000000000..2846ff5425
> --- /dev/null
> +++ b/app/test-graph/cmdline_graph.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _CMDLINE_GRAPH_H_
> +#define _CMDLINE_GRAPH_H_
> +
> +extern cmdline_parse_inst_t cmd_show_node_list;
> +extern cmdline_parse_inst_t cmd_set_lcore_config;
> +
> +extern cmdline_parse_inst_t cmd_create_graph;
> +extern cmdline_parse_inst_t cmd_destroy_graph;
> +
> +extern cmdline_parse_inst_t cmd_start_graph_walk;
> +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> +
> +extern cmdline_parse_inst_t cmd_show_graph_stats;
> +
> +#endif /* _CMDLINE_GRAPH_H_ */
> diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> new file mode 100644
> index 0000000000..d93802a975
> --- /dev/null
> +++ b/app/test-graph/meson.build
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(C) 2023 Marvell International Ltd.
> +
> +# override default name to drop the hyphen
> +name = 'test-graph'
> +cflags += '-Wno-deprecated-declarations'
> +sources = files(
> +        'cmdline.c',
> +        'cmdline_graph.c',
> +        'parameters.c',
> +        'testgraph.c',
> +)
> +
> +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> new file mode 100644
> index 0000000000..b990ca4a1c
> --- /dev/null
> +++ b/app/test-graph/parameters.c
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +
> +#include "testgraph.h"
> +
> +static const char short_options[] = "p:" /* portmask */
> +				    "P"	 /* promiscuous */
> +				    "i"	 /* interactive */
> +	;
> +
> +#define CMD_LINE_OPT_CONFIG	   "config"
> +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> +#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
> +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> +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_CONFIG_NUM,
> +	CMD_LINE_OPT_NODE_PATTERN_NUM,
> +	CMD_LINE_OPT_INTERACTIVE_NUM,
> +	CMD_LINE_OPT_NO_NUMA_NUM,
> +	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> +};
> +
> +static const struct option lgopts[] = {
> +	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> +	{CMD_LINE_OPT_NODE_PATTERN, 1, 0,
> CMD_LINE_OPT_NODE_PATTERN_NUM},
> +	{CMD_LINE_OPT_INTERACTIVE, 0, 0,
> CMD_LINE_OPT_INTERACTIVE_NUM},
> +	{CMD_LINE_OPT_NO_NUMA, 0, 0, CMD_LINE_OPT_NO_NUMA_NUM},
> +	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0,
> CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> +	{NULL, 0, 0, 0},
> +};
> +
> +/* Display usage */
> +static void
> +print_usage(const char *prgname)
> +{
> +	fprintf(stderr,
> +		"%s [EAL options] --"
> +		" -p PORTMASK"
> +		" [-P]"
> +		" [-i]"
> +		" --config (port,queue,lcore)[,(port,queue,lcore)]"
> +		" --node-pattern (node_name0,node_name1[,node_nameX)]"
> +		" [--no-numa]"
> +		" [--per-port-pool]"
> +		" [--interactive]"
> +
> +		"  -p PORTMASK: Hexadecimal bitmask of ports to configure\n"
> +		"  -P : Enable promiscuous mode\n"
> +		"  -i : Enter interactive mode\n"
> +		"  --config (port,queue,lcore): Rx queue configuration\n"
> +		"  --node-pattern (node_names): node patterns to create
> graph\n"
> +		"  --no-numa: Disable numa awareness\n"
> +		"  --per-port-pool: Use separate buffer pool per port\n"
> +		"  --interactive: Enter interactive mode\n",
> +		prgname);
> +}
> +
> +static int
> +parse_portmask(const char *portmask)
> +{
> +	char *end = NULL;
> +	unsigned long pm;
> +
> +	/* Parse hexadecimal string */
> +	pm = strtoul(portmask, &end, 16);
> +	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> +		return 0;
> +
> +	return pm;
> +}
> +
> +/* Parse the argument given in the command line of the application */
> +int
> +parse_cmdline_args(int argc, char **argv)
> +{
> +	char *prgname = argv[0];
> +	int option_index;
> +	char **argvopt;
> +	int opt, ret;
> +
> +	argvopt = argv;
> +
> +	/* Error or normal output strings. */
> +	while ((opt = getopt_long(argc, argvopt, short_options, lgopts,
> &option_index)) != EOF) {
> +
> +		switch (opt) {
> +		/* Portmask */
> +		case 'p':
> +			enabled_port_mask = parse_portmask(optarg);
> +			if (enabled_port_mask == 0) {
> +				fprintf(stderr, "Invalid portmask\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +
> +		case 'P':
> +			promiscuous_on = 1;
> +			break;
> +
> +		/* Long options */
> +		case CMD_LINE_OPT_CONFIG_NUM:
> +			ret = parse_config(optarg);
> +			if (ret) {
> +				fprintf(stderr, "Invalid config\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +		case CMD_LINE_OPT_NODE_PATTERN_NUM:
> +			ret = parse_node_patterns(optarg);
> +			if (ret) {
> +				fprintf(stderr, "Invalid node_patterns\n");
> +				print_usage(prgname);
> +				return -1;
> +			}
> +			break;
> +
> +		case CMD_LINE_OPT_INTERACTIVE_NUM:
> +		case 'i':
> +			printf("Interactive-mode selected\n");
> +			interactive = 1;
> +			break;
> +
> +		case CMD_LINE_OPT_NO_NUMA_NUM:
> +			numa_on = 0;
> +			break;
> +
> +		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> +			printf("Per port buffer pool is enabled\n");
> +			per_port_pool = 1;
> +			break;
> +
> +		default:
> +			print_usage(prgname);
> +			return -1;
> +		}
> +	}
> +
> +	if (optind >= 0)
> +		argv[optind - 1] = prgname;
> +	ret = optind - 1;
> +	optind = 1; /* Reset getopt lib */
> +
> +	return ret;
> +}
> diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> new file mode 100644
> index 0000000000..aff921acf2
> --- /dev/null
> +++ b/app/test-graph/testgraph.c
> @@ -0,0 +1,1426 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fnmatch.h>
> +#include <signal.h>
> +#include <stdlib.h>
> +
> +#include <rte_bus.h>
> +#include <rte_byteorder.h>
> +#include <rte_common.h>
> +#include <rte_dev.h>
> +#include <rte_eal.h>
> +#include <rte_ethdev.h>
> +#include <rte_ether.h>
> +#include <rte_graph_worker.h>
> +#include <rte_launch.h>
> +#include <rte_lcore.h>
> +#include <rte_log.h>
> +#include <rte_malloc.h>
> +#include <rte_mempool.h>
> +#include <rte_per_lcore.h>
> +#include <ethdev_rx_priv.h>
> +#include <ethdev_tx_priv.h>
> +#include <punt_kernel_priv.h>
> +#include <kernel_recv_priv.h>
> +
> +#include "testgraph.h"
> +
> +/* Log type */
> +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> +
> +/*
> + * Configurable number of RX/TX ring descriptors
> + */
> +#define RX_DESC_DEFAULT 1024
> +#define TX_DESC_DEFAULT 1024
> +
> +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> +#define MAX_RX_QUEUE_PER_PORT 128
> +
> +#define MAX_RX_QUEUE_PER_LCORE 16
> +
> +#define NB_SOCKETS 8
> +
> +/* Static global variables used within this file. */
> +uint16_t nb_rxd = RX_DESC_DEFAULT;
> +uint16_t nb_txd = TX_DESC_DEFAULT;
> +
> +static volatile bool force_quit;
> +volatile bool graph_walk_quit;
> +volatile bool run_graph_walk = true;
> +
> +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};
> +const char **node_patterns;
> +uint8_t num_patterns;
> +
> +uint8_t interactive; /**< interactive mode is off by default */
> +int promiscuous_on; /**< Ports set in promiscuous mode off by default. */
> +int numa_on = 1;   /**< NUMA is enabled by default. */
> +int per_port_pool; /**< Use separate buffer pools per port, disabled by default
> */
> +int testgraph_logtype; /**< Log type for testgraph logs */
> +
> +struct rte_graph_cluster_stats *stats;
> +
> +uint32_t enabled_port_mask; /**< Mask of enabled ports */
> +
> +uint32_t nb_conf;
> +
> +struct lcore_rx_queue {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +/* Lcore conf */
> +struct lcore_conf {
> +	uint16_t n_rx_queue;
> +	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> +	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> +	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
> +
> +	struct rte_graph *graph;
> +	char name[RTE_GRAPH_NAMESIZE];
> +	rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +
> +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> +static struct lcore_params lcore_params_array_default[] = {
> +	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> +	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> +};
> +
> +struct lcore_params *lcore_params = lcore_params_array_default;
> +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +static struct rte_eth_conf port_conf = {
> +	.rxmode = {
> +			.mq_mode = RTE_ETH_MQ_RX_RSS,
> +		},
> +	.rx_adv_conf = {
> +			.rss_conf = {
> +					.rss_key = NULL,
> +					.rss_hf = RTE_ETH_RSS_IP,
> +				},
> +		},
> +	.txmode = {
> +			.mq_mode = RTE_ETH_MQ_TX_NONE,
> +		},
> +};
> +
> +static const struct node_list test_node_list[] = {{{"ethdev_rx", "ethdev_tx"}, 0,
> 2},
> +						{{"ethdev_rx", "punt_kernel"},
> 0, 2},
> +						{{"kernel_recv", "ethdev_tx"},
> 0, 2} };
> +

Here we support 3 topo, could you support the original l3fwd nodes first?

> +static const struct node_list supported_nodes[] = {{{"ethdev_rx"},
> TEST_GRAPH_ETHDEV_RX_NODE, 1},
> +						{{"ethdev_tx"},
> TEST_GRAPH_ETHDEV_TX_NODE, 1},
> +						{{"punt_kernel"},
> TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> +						{{"kernel_recv"},
> TEST_GRAPH_KERNEL_RECV_NODE, 1},
> +						{{"ip4_lookup"},
> TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> +						{{"ip4_rewrite"},
> TEST_GRAPH_IP4_REWRITE_NODE, 1},
> +						{{"pkt_cls"},
> TEST_GRAPH_PKT_CLS_NODE, 1},
> +						{{"pkt_drop"},
> TEST_GRAPH_PKT_DROP_NODE, 1},
> +						{{"NULL"},
> TEST_GRAPH_NULL_NODE, 1} };
> +
> +static struct rte_mempool
> *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> +
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +
> +static int
> +check_lcore_params(void)
> +{
> +	uint8_t queue, lcore;
> +	int socketid;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		queue = lcore_params[i].queue_id;
> +		if (queue >= MAX_RX_QUEUE_PER_PORT) {
> +			printf("Invalid queue number: %hhu\n", queue);
> +			return -1;
> +		}
> +		lcore = lcore_params[i].lcore_id;
> +		if (!rte_lcore_is_enabled(lcore)) {
> +			printf("Error: lcore %hhu is not enabled in lcore mask\n",
> lcore);
> +			return -1;
> +		}
> +
> +		if (lcore == rte_get_main_lcore()) {
> +			printf("Error: lcore %u is main lcore\n", lcore);
> +			return -1;
> +		}
> +		socketid = rte_lcore_to_socket_id(lcore);
> +		if ((socketid != 0) && (numa_on == 0)) {
> +			printf("Warning: lcore %hhu is on socket %d with numa
> off\n", lcore,
> +			       socketid);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +check_port_config(void)
> +{
> +	uint16_t portid;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		portid = lcore_params[i].port_id;
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("Port %u is not enabled in port mask\n", portid);
> +			return -1;
> +		}
> +		if (!rte_eth_dev_is_valid_port(portid)) {
> +			printf("Port %u is not present on the board\n", portid);
> +			return -1;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static uint8_t
> +get_port_n_rx_queues(const uint16_t port)
> +{
> +	int queue = -1;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		if (lcore_params[i].port_id == port) {
> +			if (lcore_params[i].queue_id == queue + 1)
> +				queue = lcore_params[i].queue_id;
> +			else
> +				rte_exit(EXIT_FAILURE, "Queue ids of the
> port %d must be"
> +					 " in sequence and must start with 0\n",
> +					 lcore_params[i].port_id);
> +		}
> +	}
> +
> +	return (uint8_t)(++queue);
> +}
> +
> +static int
> +init_lcore_rx_queues(void)
> +{
> +	uint16_t i, nb_rx_queue;
> +	uint8_t lcore;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		lcore = lcore_params[i].lcore_id;
> +		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> +		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> +			printf("Error: too many queues (%u) for lcore: %u\n",
> +			       (unsigned int)nb_rx_queue + 1, (unsigned int)lcore);
> +			return -1;
> +		}
> +
> +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id =
> lcore_params[i].port_id;
> +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id =
> lcore_params[i].queue_id;
> +		lcore_conf[lcore].n_rx_queue++;
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +validate_config(void)
> +{
> +	int rc = -1;
> +
> +	if (check_lcore_params() < 0) {
> +		printf("check_lcore_params() failed\n");
> +		goto exit;
> +	}
> +
> +	if (init_lcore_rx_queues() < 0) {
> +		printf("init_lcore_rx_queues() failed\n");
> +		goto exit;
> +	}
> +
> +	if (check_port_config() < 0) {
> +		printf("check_port_config() failed\n");
> +		goto exit;
> +	}
> +
> +	return 0;
> +
> +exit:
> +	return rc;
> +}
> +
> +#define MEMPOOL_CACHE_SIZE 256
> +
> +/*
> + * This expression is used to calculate the number of mbufs needed
> + * depending on user input, taking  into account memory for rx and
> + * tx hardware rings, cache per lcore and mtable per port per lcore.
> + * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum
> + * value of 8192
> + */
> +#define NB_MBUF(nports)                                                                            \
> +	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores *
> RTE_GRAPH_BURST_SIZE +       \
> +		 nports * nb_tx_queue * nb_txd + nb_lcores *
> MEMPOOL_CACHE_SIZE),                  \
> +		8192u)
> +
> +static int
> +init_mem(uint16_t portid, uint32_t nb_mbuf)
> +{
> +	uint32_t lcore_id;
> +	int socketid;
> +	char s[64];
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		if (numa_on)
> +			socketid = rte_lcore_to_socket_id(lcore_id);
> +		else
> +			socketid = 0;
> +
> +		if (socketid >= NB_SOCKETS) {
> +			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out of
> range %d\n",
> +				 socketid, lcore_id, NB_SOCKETS);
> +		}
> +
> +		if (pktmbuf_pool[portid][socketid] == NULL) {
> +			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid,
> socketid);
> +			/* Create a pool with priv size of a cacheline */
> +			pktmbuf_pool[portid][socketid] =
> rte_pktmbuf_pool_create(
> +				s, nb_mbuf, MEMPOOL_CACHE_SIZE,
> RTE_CACHE_LINE_SIZE,
> +				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> +			if (pktmbuf_pool[portid][socketid] == NULL)
> +				rte_exit(EXIT_FAILURE, "Cannot init mbuf pool
> on socket %d\n",
> +					 socketid);
> +			else
> +				printf("Allocated mbuf pool on socket %d\n",
> socketid);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +	uint8_t count, all_ports_up, print_flag = 0;
> +	struct rte_eth_link link;
> +	uint16_t portid;
> +	int ret;
> +	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> +
> +	printf("\nChecking link status");
> +	fflush(stdout);
> +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +		if (force_quit)
> +			return;
> +		all_ports_up = 1;
> +		RTE_ETH_FOREACH_DEV(portid) {
> +			if (force_quit)
> +				return;
> +			if ((port_mask & (1 << portid)) == 0)
> +				continue;
> +			memset(&link, 0, sizeof(link));
> +			ret = rte_eth_link_get_nowait(portid, &link);
> +			if (ret < 0) {
> +				all_ports_up = 0;
> +				if (print_flag == 1)
> +					printf("Port %u link get failed: %s\n",
> portid,
> +					       rte_strerror(-ret));
> +				continue;
> +			}
> +			/* Print link status if flag set */
> +			if (print_flag == 1) {
> +				rte_eth_link_to_str(link_status_text,
> sizeof(link_status_text),
> +						    &link);
> +				printf("Port %d %s\n", portid, link_status_text);
> +				continue;
> +			}
> +			/* Clear all_ports_up flag if any link down */
> +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> +				all_ports_up = 0;
> +				break;
> +			}
> +		}
> +		/* After finally printing all link status, get out */
> +		if (print_flag == 1)
> +			break;
> +
> +		if (all_ports_up == 0) {
> +			printf(".");
> +			fflush(stdout);
> +			rte_delay_ms(CHECK_INTERVAL);
> +		}
> +
> +		/* Set the print_flag if all ports up or timeout */
> +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +			print_flag = 1;
> +			printf("Done\n");
> +		}
> +	}
> +}
> +
> +static void
> +signal_handler(int signum)
> +{
> +	if (signum == SIGINT || signum == SIGTERM) {
> +		printf("\n\nSignal %d received, preparing to exit...\n", signum);
> +		force_quit = true;
> +	}
> +	prompt_exit();
> +}
> +
> +int
> +graph_main_loop(void *conf)
> +{
> +	struct lcore_conf *qconf;
> +	struct rte_graph *graph;
> +	uint32_t lcore_id;
> +
> +	RTE_SET_USED(conf);
> +
> +	lcore_id = rte_lcore_id();
> +	qconf = &lcore_conf[lcore_id];
> +	graph = qconf->graph;
> +
> +	if (!graph) {
> +		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to do\n",
> lcore_id);
> +		return 0;
> +	}
> +
> +	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u,
> graph %s(%p)\n", lcore_id,
> +		qconf->name, graph);
> +
> +	while (likely(!force_quit && !graph_walk_quit)) {
> +		if (likely(run_graph_walk))
> +			rte_graph_walk(graph);
> +	}

graph_walk_quit could be changed to true in destroy stage,  we should set it false in create stage also.
Otherwise, it will walk only once for interactive-mode.

I am agree to use different switch for different stage to start/stop, create/destroy and termination. 
force_quit + graph_walk_quit + run_graph_walk looks little overcomplicated. Could it only use
force_quit to pause the walk in worker?

> +
> +	return 0;
> +}
> +
> +struct rte_graph_cluster_stats *
> +create_graph_cluster_stats(void)
> +{
> +	struct rte_graph_cluster_stats_param s_param;
> +	struct rte_graph_cluster_stats *stat;
> +	const char *pattern = "worker_*";
> +
> +	/* 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;
> +
> +	stat = rte_graph_cluster_stats_create(&s_param);
> +	if (stat == NULL)
> +		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> +
> +	return stat;
> +}
> +
> +static void
> +print_stats(void)
> +{
> +	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> +	const char clr[] = {27, '[', '2', 'J', '\0'};
> +
> +	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);
> +}
> +
> +int
> +parse_config(const char *q_arg)
> +{
> +	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE,
> _NUM_FLD };
> +	unsigned long int_fld[_NUM_FLD];
> +	const char *p, *p0 = q_arg;
> +	char *str_fld[_NUM_FLD];
> +	uint32_t size;
> +	char s[256];
> +	char *end;
> +	int i;
> +
> +	nb_lcore_params = 0;
> +
> +	while ((p = strchr(p0, '(')) != NULL) {
> +		++p;
> +		p0 = strchr(p, ')');
> +		if (p0 == NULL)
> +			goto exit;
> +
> +		size = p0 - p;
> +		if (size >= sizeof(s))
> +			goto exit;
> +
> +		memcpy(s, p, size);
> +		s[size] = '\0';
> +		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD)
> +			goto exit;
> +		for (i = 0; i < _NUM_FLD; i++) {
> +			errno = 0;
> +			int_fld[i] = strtoul(str_fld[i], &end, 0);
> +			if (errno != 0 || end == str_fld[i])
> +				goto exit;
> +		}
> +
> +		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> +			printf("Exceeded max number of lcore params: %hu\n",
> nb_lcore_params);
> +			goto exit;
> +		}
> +
> +		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS ||
> int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> +			printf("Invalid port/lcore id\n");
> +			goto exit;
> +		}
> +
> +		lcore_params_array[nb_lcore_params].port_id =
> (uint8_t)int_fld[FLD_PORT];
> +		lcore_params_array[nb_lcore_params].queue_id =
> (uint8_t)int_fld[FLD_QUEUE];
> +		lcore_params_array[nb_lcore_params].lcore_id =
> (uint8_t)int_fld[FLD_LCORE];
> +		++nb_lcore_params;
> +	}
> +	lcore_params = lcore_params_array;
> +
> +	return 0;
> +exit:
> +	/* Revert to default config */
> +	lcore_params = lcore_params_array_default;
> +	nb_lcore_params = RTE_DIM(lcore_params_array_default);
> +
> +	return -1;
> +}
> +
> +int
> +parse_node_patterns(const char *q_arg)
> +{
> +	const char *p, *p0 = q_arg;
> +	int ret = -EINVAL;
> +	uint32_t size;
> +
> +	num_patterns = 0;
> +
> +	p = strchr(p0, '(');
> +	if (p != NULL) {
> +		++p;
> +		while ((p0 = strchr(p, ',')) != NULL) {
> +			size = p0 - p;
> +			if (size >= RTE_NODE_NAMESIZE)
> +				goto exit;
> +
> +			if (num_patterns >= MAX_NODE_PATTERNS) {
> +				printf("Too many nodes passed.\n");
> +				goto exit;
> +			}
> +
> +			memcpy(node_pattern[num_patterns++], p, size);

Here, if not make node_pattern clean, node_pattern[0](size,MAX) will be tainted by previous create.

> +			p = p0 + 1;
> +		}
> +
> +		p0 = strchr(p, ')');
> +		if (p0 != NULL) {
> +			size = p0 - p;
> +			if (size >= RTE_NODE_NAMESIZE)
> +				goto exit;
> +
> +			if (num_patterns >= MAX_NODE_PATTERNS) {
> +				printf("Too many nodes passed.\n");
> +				goto exit;
> +			}
> +
> +			memcpy(node_pattern[num_patterns++], p, size);
> +		} else {
> +			goto exit;
> +		}
> +	} else {
> +		goto exit;
> +	}
> +
> +	return 0;
> +exit:
> +	return ret;
> +}
> +
> +static void
> +set_default_node_pattern(void)
> +{
> +	uint16_t idx;
> +
> +	for (idx = 0; idx < test_node_list[0].size; idx++)
> +		strcpy(node_pattern[num_patterns++],
> test_node_list[0].nodes[idx]);
> +}
> +
> +int
> +validate_node_names(uint64_t *valid_nodes)

Actually, this func limits the node name to follow  test_node_list.
It's OK if there are few nodes/edges. 

But how to describe much nodes/edges like
graph_dot {
  A->B;
  A->C;
  B->D;
  C->D;
}

If update edges implicitly, there is no need to specific the node sequence. Both of (A,B,C,D) and (A,C,B,D) are correct.

> +{
> +	rte_node_t node_cnt = rte_node_max_count();
> +	bool pattern_matched = false;
> +	rte_node_t id = 0;
> +	int ret = -EINVAL;
> +	uint16_t idx, i, j;
> +
> +	for (idx = 0; idx < num_patterns; idx++) {
> +		for (id = 0; id < node_cnt; id++) {
> +			if (strncmp(node_pattern[idx],
> rte_node_id_to_name(id),
> +				    RTE_GRAPH_NAMESIZE) == 0)
> +				break;
> +		}
> +		if (node_cnt == id) {
> +			printf("Invalid node name passed\n");
> +			return ret;
> +		}
> +	}
> +
> +	for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +		idx = 0;
> +		if (test_node_list[i].size == num_patterns) {
> +			for (j = 0; j < num_patterns; j++) {
> +				if (strncmp(node_pattern[j],
> test_node_list[i].nodes[j],
> +				    RTE_GRAPH_NAMESIZE) == 0)
> +					idx++;
> +			}
> +			if (idx == num_patterns)
> +				pattern_matched = true;
> +		}
> +	}
> +
> +	if (!pattern_matched) {
> +		printf("Unsupported node pattern passed\n\n");
> +		printf("Test supported node patterns are:\n");
> +		for (i = 0; i < RTE_DIM(test_node_list); i++) {
> +			printf("(");
> +			for (j = 0; j < (test_node_list[i].size - 1); j++)
> +				printf("%s,", test_node_list[i].nodes[j]);
> +			printf("%s", test_node_list[i].nodes[j]);
> +			printf(")\n");
> +		}
> +
> +		return ret;
> +	}
> +
> +	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> +		for (j = 0; j < num_patterns; j++) {
> +			if (strncmp(node_pattern[j],
> supported_nodes[i].nodes[0],
> +				    RTE_GRAPH_NAMESIZE) == 0) {
> +				*valid_nodes |= supported_nodes[i].test_id;
> +				break;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void
> +cleanup_node_pattern(void)
> +{
> +	while (num_patterns) {
> +		memset(node_pattern[num_patterns - 1], 0,
> RTE_GRAPH_NAMESIZE);
> +		num_patterns--;
> +	}
> +}
> +
> +static int
> +ethdev_tx_node_configure(void)
> +{
> +	struct ethdev_tx_node_main *tx_node_data;
> +	struct rte_node_register *tx_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	uint16_t port_id;
> +	uint32_t id;
> +
> +	tx_node_data = ethdev_tx_node_data_get();
> +	tx_node = ethdev_tx_node_get();
> +
> +	RTE_ETH_FOREACH_DEV(port_id) {
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << port_id)) == 0) {
> +			printf("\nSkipping disabled port %d\n", port_id);
> +			continue;
> +		}
> +
> +		if (!rte_eth_dev_is_valid_port(port_id))
> +			return -EINVAL;
> +
> +		/* Create a per port tx node from base node */
> +		snprintf(name, sizeof(name), "%u", port_id);
> +		id = rte_node_clone(tx_node->id, name);
> +		tx_node_data->nodes[port_id] = id;
> +
> +		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node->name,
> name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +punt_kernel_node_configure(void)
> +{
> +	struct rte_node_register *punt_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	uint32_t id;
> +
> +	punt_node = punt_kernel_node_get();
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Create a per lcore punt_kernel node from base node */
> +		snprintf(name, sizeof(name), "%u", lcore_id);
> +		id = rte_node_clone(punt_node->id, name);
> +		strcpy(qconf->punt_kernel_node_name,
> rte_node_id_to_name(id));
> +
> +		printf("punt_kernel node %s-%s: is at %u\n", punt_node->name,
> name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +kernel_recv_node_configure(void)
> +{
> +	struct rte_node_register *recv_node;
> +	char name[RTE_NODE_NAMESIZE];
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	uint32_t id;
> +
> +	recv_node = kernel_recv_node_get();
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Create a per lcore kernel_recv node from base node */
> +		snprintf(name, sizeof(name), "%u", lcore_id);
> +		id = rte_node_clone(recv_node->id, name);
> +		strcpy(qconf->kernel_recv_node_name,
> rte_node_id_to_name(id));
> +
> +		printf("kernel_recv node %s-%s: is at %u\n", recv_node->name,
> name, id);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t
> nb_confs)
> +{
> +	char name[RTE_NODE_NAMESIZE];
> +	uint16_t i, j, port_id;
> +	uint32_t id;
> +
> +	for (i = 0; i < nb_confs; i++) {
> +		port_id = conf[i].port_id;
> +
> +		if (!rte_eth_dev_is_valid_port(port_id))
> +			return -EINVAL;
> +
> +		/* Create node for each rx port queue pair */
> +		for (j = 0; j < conf[i].num_rx_queues; j++) {
> +			struct ethdev_rx_node_main *rx_node_data;
> +			struct rte_node_register *rx_node;
> +			ethdev_rx_node_elem_t *elem;
> +
> +			rx_node_data = ethdev_rx_get_node_data_get();
> +			rx_node = ethdev_rx_node_get();
> +			snprintf(name, sizeof(name), "%u-%u", port_id, j);
> +			/* Clone a new rx node with same edges as parent */
> +			id = rte_node_clone(rx_node->id, name);
> +			if (id == RTE_NODE_ID_INVALID)
> +				return -EIO;
> +
> +			/* Add it to list of ethdev rx nodes for lookup */
> +			elem = malloc(sizeof(ethdev_rx_node_elem_t));
> +			if (elem == NULL)
> +				return -ENOMEM;
> +			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> +			elem->ctx.port_id = port_id;
> +			elem->ctx.queue_id = j;
> +			elem->nid = id;
> +			elem->next = rx_node_data->head;
> +			rx_node_data->head = elem;
> +
> +			printf("ethdev:: Rx node %s-%s: is at %u\n", rx_node-
> >name, name, id);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> +{
> +	struct ethdev_rx_node_main *rx_node_data;
> +	ethdev_rx_node_elem_t *elem;
> +	char *next_nodes[16];
> +	rte_edge_t count;
> +	uint16_t i;
> +
> +	count = rte_node_edge_count(id);
> +	rte_node_edge_get(id, next_nodes);
> +
> +	for (i = 0; i < count; i++) {
> +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> +			rx_node_data = ethdev_rx_get_node_data_get();
> +			elem = rx_node_data->head;
> +			while (elem->next != rx_node_data->head) {
> +				if (elem->nid == id)
> +					break;
> +				elem = elem->next;
> +			}
> +
> +			if (elem->nid == id)
> +				elem->ctx.cls_next = i;
> +			break;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_tx_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"ethdev_tx-*"};
> +	uint16_t queue, queue_id, port_id;
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	uint32_t rx_id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +		port_id = qconf->rx_queue_list[queue].port_id;
> +		queue_id = qconf->rx_queue_list[queue].queue_id;
> +
> +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id,
> queue_id);
> +		rx_id = rte_node_from_name(name);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		/* Prepare the actual name of the cloned node */
> +		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> +
> +		/* Update ethdev_rx node edges */
> +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> &next_node, 1);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		/* Update node_next details */
> +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"punt_kernel-*"};
> +	uint16_t queueid, portid, queue;
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	rte_node_t rx_id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +
> +		portid = qconf->rx_queue_list[queue].port_id;
> +		queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid,
> queueid);
> +		rx_id = rte_node_from_name(name);
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], name);
> +
> +		next_node = qconf->punt_kernel_node_name;
> +
> +		/* Fill node pattern */
> +		strcpy(node_pattern[num_patterns++], next_node);
> +
> +		/* Update ethdev_rx node edges */
> +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> &next_node, 1);
> +
> +		/* Update node_next details */
> +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
> +{
> +	struct kernel_recv_node_main *rx_node_data;
> +	kernel_recv_node_elem_t *elem;
> +	char *next_nodes[16];
> +	rte_edge_t count;
> +	uint16_t i;
> +
> +	count = rte_node_edge_count(id);
> +	rte_node_edge_get(id, next_nodes);
> +
> +	for (i = 0; i < count; i++) {
> +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> +			rx_node_data = kernel_recv_node_data_get();
> +			elem = rx_node_data->head;
> +			while (elem->next != rx_node_data->head) {
> +				if (elem->nid == id)
> +					break;
> +				elem = elem->next;
> +			}
> +
> +			if (elem->nid == id)
> +				elem->ctx.recv_info->cls_next = i;
> +			break;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
> +{
> +	const char * const pattern[] = {"ethdev_tx-*"};
> +	char name[RTE_NODE_NAMESIZE];
> +	const char *next_node = name;
> +	struct lcore_conf *qconf;
> +	uint16_t port_id;
> +	rte_node_t id;
> +
> +	qconf = &lcore_conf[lcore_id];
> +
> +	id = rte_node_from_name(qconf->kernel_recv_node_name);
> +
> +	/* Fill node pattern */
> +	strcpy(node_pattern[num_patterns++], qconf-
> >kernel_recv_node_name);
> +
> +	port_id = lcore_id;
> +
> +	if ((enabled_port_mask & (1 << port_id)) == 0) {
> +		/* Use available port_id */
> +		RTE_ETH_FOREACH_DEV(port_id) {
> +			if ((enabled_port_mask & (1 << port_id)) != 0)
> +				break;
> +		}
> +	}
> +
> +	if (!rte_eth_dev_is_valid_port(port_id)) {
> +		printf("Port %u is not present on the board\n", port_id);
> +		return -1;
> +	}
> +
> +	/* Prepare the actual name of the cloned node */
> +	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> +
> +	/* Update ethdev_rx node edges */
> +	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node, 1);
> +
> +	/* Fill node pattern */
> +	strcpy(node_pattern[num_patterns++], name);
> +
> +	/* Update node_next details */
> +	update_kernel_recv_node_next(id, pattern[0]);
> +
> +	return 0;
> +}

These explicitly update node and link could be put in a independent stage, like graph_config.

Also, I wonder if update link could be more generic. Cause they have some similar steps.

BTY, In this patch, it could support rx->tx first.
Then add a new patch to support other topo(kernel_recv, punt_kernel and l3fwd). 
And we could enable some help function in another patch.
It could make the patch small. 

> +
> +uint32_t
> +ethdev_ports_setup(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	uint32_t nb_tx_queue, nb_lcores;
> +	uint32_t nb_ports, nb_conf = 0;
> +	uint8_t nb_rx_queue;
> +	uint16_t portid;
> +	int ret;
> +
> +	nb_ports = rte_eth_dev_count_avail();
> +	nb_lcores = rte_lcore_count();
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		struct rte_eth_conf local_port_conf = port_conf;
> +
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("\nSkipping disabled port %d\n", portid);
> +			continue;
> +		}
> +
> +		/* Init port */
> +		printf("Initializing port %d ... ", portid);
> +		fflush(stdout);
> +
> +		nb_rx_queue = get_port_n_rx_queues(portid);
> +		nb_tx_queue = nb_lcores;
> +		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> +			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> +		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n",
> nb_rx_queue, nb_tx_queue);
> +
> +		rte_eth_dev_info_get(portid, &dev_info);
> +
> +		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 !=
> +		    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, 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_rx_queue, nb_tx_queue,
> &local_port_conf);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "Cannot configure device:
> err=%d, port=%d\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=%d\n",
> +				 ret, portid);
> +
> +		/* Init memory */
> +		if (!per_port_pool)
> +			ret = init_mem(0, NB_MBUF(nb_ports));
> +		else
> +			ret = init_mem(portid, NB_MBUF(1));
> +
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> +
> +		/* Setup ethdev node config */
> +		ethdev_conf[nb_conf].port_id = portid;
> +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> +		if (!per_port_pool)
> +			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> +
> +		else
> +			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> +		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> +
> +		nb_conf++;
> +	}
> +
> +	return nb_conf;
> +}
> +
> +void
> +ethdev_rxq_configure(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	uint16_t queueid, portid;
> +	struct lcore_conf *qconf;
> +	uint8_t queue, socketid;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +		qconf = &lcore_conf[lcore_id];
> +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +		fflush(stdout);
> +
> +		/* Init RX queues */
> +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +			struct rte_eth_rxconf rxq_conf;
> +
> +			portid = qconf->rx_queue_list[queue].port_id;
> +			queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +			if (numa_on)
> +				socketid =
> (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +			else
> +				socketid = 0;
> +
> +			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> +			fflush(stdout);
> +
> +			rte_eth_dev_info_get(portid, &dev_info);
> +			rxq_conf = dev_info.default_rxconf;
> +			rxq_conf.offloads = port_conf.rxmode.offloads;
> +			if (!per_port_pool)
> +				ret = rte_eth_rx_queue_setup(portid, queueid,
> nb_rxd, socketid,
> +							     &rxq_conf,
> pktmbuf_pool[0][socketid]);
> +			else
> +				ret = rte_eth_rx_queue_setup(portid, queueid,
> nb_rxd, socketid,
> +							     &rxq_conf,
> +
> pktmbuf_pool[portid][socketid]);
> +			if (ret < 0)
> +				rte_exit(EXIT_FAILURE,
> "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> +					 ret, portid);
> +
> +			snprintf(qconf->rx_queue_list[queue].node_name,
> RTE_NODE_NAMESIZE,
> +				 "ethdev_rx-%u-%u", portid, queueid);
> +		}
> +	}
> +	printf("\n");
> +}
> +
> +void
> +ethdev_txq_configure(void)
> +{
> +	struct rte_eth_dev_info dev_info;
> +	struct rte_eth_txconf *txconf;
> +	uint16_t queueid, portid;
> +	uint32_t lcore_id;
> +	uint8_t socketid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		struct rte_eth_conf local_port_conf = port_conf;
> +
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("\nSkipping disabled port %d\n", portid);
> +			continue;
> +		}
> +
> +		rte_eth_dev_info_get(portid, &dev_info);
> +
> +		/* Init one TX queue per (lcore,port) pair */
> +		queueid = 0;
> +		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +			if (rte_lcore_is_enabled(lcore_id) == 0)
> +				continue;
> +
> +			if (numa_on)
> +				socketid =
> (uint8_t)rte_lcore_to_socket_id(lcore_id);
> +			else
> +				socketid = 0;
> +
> +			printf("txq=%u,%d,%d ", lcore_id, queueid, socketid);
> +			fflush(stdout);
> +
> +			txconf = &dev_info.default_txconf;
> +			txconf->offloads = local_port_conf.txmode.offloads;
> +			ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd,
> socketid, txconf);
> +			if (ret < 0)
> +				rte_exit(EXIT_FAILURE,
> "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> +					 ret, portid);
> +			queueid++;
> +		}
> +	}
> +	printf("\n");
> +}
> +
> +int
> +configure_graph_nodes(uint64_t valid_nodes)
> +{
> +	int ret = 0;
> +
> +	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> +		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> +		if (ret) {
> +			printf("ethdev_rx_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> +		ret = ethdev_tx_node_configure();
> +		if (ret) {
> +			printf("ethdev_tx_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> +		ret = punt_kernel_node_configure();
> +		if (ret) {
> +			printf("punt_kernel_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
> +		ret = kernel_recv_node_configure();
> +		if (ret) {
> +			printf("kernel_recv_node_configure: err=%d\n", ret);
> +			goto exit;
> +		}
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static int
> +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> +{
> +	int ret = 0;
> +
> +	num_patterns = 0;
> +
> +	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> TEST_GRAPH_ETHDEV_RX_NODE)) {
> +		ret = link_ethdev_rx_to_tx_node(lcore_id);
> +		if (ret) {
> +			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> +			goto exit;
> +		}
> +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE |
> TEST_GRAPH_PUNT_KERNEL_NODE)) {
> +		link_ethdev_rx_to_punt_kernel_node(lcore_id);
> +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> TEST_GRAPH_KERNEL_RECV_NODE)) {
> +		link_kernel_recv_to_ethdev_tx_node(lcore_id);
> +	} else {
> +		printf("Invalid node map\n");
> +		ret = -EINVAL;
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +void
> +start_eth_ports(void)
> +{
> +	uint16_t portid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +
> +		ret = rte_eth_dev_start(portid);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d,
> port=%d\n", ret, portid);
> +
> +		if (promiscuous_on)
> +			rte_eth_promiscuous_enable(portid);
> +	}
> +}
> +
> +void
> +stop_eth_ports(void)
> +{
> +	uint16_t portid;
> +	int ret;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +		printf("Closing port %d...", portid);
> +		ret = rte_eth_dev_stop(portid);
> +		if (ret != 0)
> +			printf("Failed to stop port %u: %s\n", portid,
> rte_strerror(-ret));
> +		rte_eth_dev_close(portid);
close should be in exit stage or when application is killed.

> +	}
> +}
> +
> +int
> +create_graph(uint64_t valid_nodes)
> +{
> +	struct rte_graph_param graph_conf;
> +	struct lcore_conf *qconf;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	node_patterns = malloc(MAX_NODE_PATTERNS *
> sizeof(*node_patterns));
> +	if (!node_patterns)
> +		return -ENOMEM;
> +
> +	memset(&graph_conf, 0, sizeof(graph_conf));
> +	graph_conf.node_patterns = node_patterns;
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		rte_graph_t graph_id;
> +		rte_edge_t i;
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Skip graph creation if no source exists */
> +		if (!qconf->n_rx_queue)
> +			continue;
> +
> +		ret = link_graph_nodes(valid_nodes, lcore_id);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "link_graph_nodes(): failed\n");
> +
> +		for (i = 0; i < num_patterns; i++) {
> +			graph_conf.node_patterns[i] = node_pattern[i];
> +			printf("%s,", graph_conf.node_patterns[i]);
> +		}
> +		printf("\n");
> +
> +		graph_conf.nb_node_patterns = num_patterns;
> +
> +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> lcore_id);
> +
> +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> +		if (graph_id == RTE_GRAPH_ID_INVALID)
> +			rte_exit(EXIT_FAILURE, "rte_graph_create(): graph_id
> invalid for lcore%u\n",
> +				 lcore_id);
> +
> +		qconf->graph_id = graph_id;
> +		qconf->graph = rte_graph_lookup(qconf->name);
> +
> +		if (!qconf->graph)
> +			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph %s
> not found\n",
> +				 qconf->name);
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +destroy_graph(void)
> +{
> +	uint32_t lcore_id;
> +	rte_graph_t id;
> +	int ret;
> +
> +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> +	{
> +		if (lcore_conf[lcore_id].graph) {
> +			id = rte_graph_from_name(lcore_conf[lcore_id].name);
> +			if (rte_graph_destroy(id)) {
> +				printf("graph_id %u destroy failed.\n", id);
> +				ret = -1;
> +			}
> +		}
> +	}
> +
> +	if (node_patterns)
> +		free(node_patterns);

Cause this cmd could be called twice in interactive-mode, we should set it to NULL after free.

> +
> +	return ret;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +	uint64_t valid_nodes;
> +	uint32_t lcore_id;
> +	int ret;
> +
> +	graph_walk_quit = false;
> +	force_quit = false;
> +	interactive = 0;
> +
> +	node_patterns = NULL;
> +
> +	signal(SIGINT, signal_handler);
> +	signal(SIGTERM, signal_handler);
> +
> +	testgraph_logtype = rte_log_register("testgraph");
> +	if (testgraph_logtype < 0)
> +		rte_exit(EXIT_FAILURE, "Cannot register log type");
> +
> +	set_default_node_pattern();
> +
> +	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> +
> +	ret = rte_eal_init(argc, argv);
> +	if (ret < 0)
> +		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n",
> rte_strerror(rte_errno));
> +	argc -= ret;
> +	argv += ret;
> +
> +	if (argc > 1) {
> +		ret = parse_cmdline_args(argc, argv);
> +		if (ret < 0)
> +			rte_exit(EXIT_FAILURE, "Invalid command line
> parameters\n");
> +	}
> +
> +#ifdef RTE_LIB_CMDLINE
> +	if (init_cmdline() != 0)
> +		rte_exit(EXIT_FAILURE, "Could not initialise cmdline
> context.\n");
> +
> +	if (interactive == 1) {
> +		prompt();
> +	} else
> +#endif
> +	{
> +		if (validate_config() < 0)
> +			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> +
> +		ret = validate_node_names(&valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "validate_node_names:
> err=%d\n", ret);
> +
> +		nb_conf = ethdev_ports_setup();
> +
> +		ethdev_rxq_configure();
> +
> +		ethdev_txq_configure();
> +
> +		ret = configure_graph_nodes(valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> err=%d\n", ret);
> +
> +		ret = create_graph(valid_nodes);
> +		if (ret)
> +			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> +
> +		stats = create_graph_cluster_stats();
> +		if (stats == NULL)
> +			rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> failed\n");
> +
> +		check_all_ports_link_status(enabled_port_mask);
> +
> +		start_eth_ports();
> +
> +		/* Launch per-lcore init on every worker lcore */
> +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> SKIP_MAIN);
> +
> +		/* Accumulate and print stats on main until exit */
> +		if (rte_graph_has_stats_feature())
> +			print_stats();
> +
> +		/* Wait for worker cores to exit */
> +		RTE_LCORE_FOREACH_WORKER(lcore_id) {
> +			ret = rte_eal_wait_lcore(lcore_id);
> +			if (ret < 0)
> +				break;
> +		}
> +
> +		ret = destroy_graph();
> +
> +		stop_eth_ports();
> +	}
> +
> +	/* clean up the EAL */
> +	ret = rte_eal_cleanup();
> +	if (ret != 0)
> +		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-
> ret));
> +
> +	return EXIT_SUCCESS;
> +}
> diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> new file mode 100644
> index 0000000000..ea1f8ef4fa
> --- /dev/null
> +++ b/app/test-graph/testgraph.h
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef _TESTGRAPH_H_
> +#define _TESTGRAPH_H_
> +
> +#include <stdbool.h>
> +
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include <cmdline.h>
> +#include <cmdline_parse.h>
> +
> +
> +#define MAX_LCORE_PARAMS 1024
> +#define MAX_NODE_PATTERNS 128
> +
> +#ifndef BIT_ULL
> +#define BIT_ULL(nr) (1ULL << (nr))
> +#endif
> +
> +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> +
> +static volatile bool force_quit;
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern uint32_t enabled_port_mask;
> +extern uint32_t nb_conf;
> +
> +extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by
> default. */
> +extern uint8_t interactive;	   /**< interactive mode is disabled by default.
> */
> +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> +extern int numa_on;		   /**< NUMA is enabled by default. */
> +extern int per_port_pool;
> +
> +extern volatile bool graph_walk_quit;
> +extern volatile bool run_graph_walk;
> +extern struct rte_graph_cluster_stats *stats;
> +
> +extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> +extern uint8_t num_patterns;
> +
> +struct node_list {
> +	const char *nodes[MAX_NODE_PATTERNS];
> +	uint64_t test_id;
> +	uint8_t size;
> +};
> +
> +struct lcore_params {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +void prompt(void);
> +void prompt_exit(void);
> +int init_cmdline(void);
> +int validate_config(void);
> +int parse_cmdline_args(int argc, char **argv);
> +uint32_t ethdev_ports_setup(void);
> +void ethdev_rxq_configure(void);
> +void ethdev_txq_configure(void);
> +void start_eth_ports(void);
> +void stop_eth_ports(void);
> +int create_graph(uint64_t valid_nodes);
> +int destroy_graph(void);
> +int graph_main_loop(void *conf);
> +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> +void check_all_ports_link_status(uint32_t port_mask);
> +int configure_graph_nodes(uint64_t valid_nodes);
> +
> +int parse_config(const char *q_arg);
> +int parse_node_patterns(const char *q_arg);
> +int validate_node_names(uint64_t *valid_nodes);
> +void cleanup_node_pattern(void);
> +
> +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> +	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt, ##args)
> +
> +#endif /* _TESTGRAPH_H_ */
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index 6f84fc31ff..f18c508fa2 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -20,6 +20,7 @@ DPDK Tools User Guides
>      cryptoperf
>      comp_perf
>      testeventdev
> +    testgraph
>      testregex
>      testmldev
>      dts
> diff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst
> new file mode 100644
> index 0000000000..3c1e058724
> --- /dev/null
> +++ b/doc/guides/tools/testgraph.rst
> @@ -0,0 +1,131 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(C) 2023 Marvell International Ltd.
> +
> +dpdk-test-graph Application
> +===========================
> +
> +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK) application
> that allows
> +exercising various graph library features. This application has a generic
> framework to add
> +new test configurations and expand test coverage to verify the functionality of
> graph nodes
> +and observe the graph cluster statistics.
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options:
> +
> +.. code-block:: console
> +
> +   dpdk-test-eventdev [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +The following are the EAL command-line options that can be used in
> conjunction
> +with the ``dpdk-test-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> +        list of cores to use.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +The following are the application command-line options:
> +
> +* ``-p <n>``
> +
> +        Set the ethdev port mask.
> +
> +* ``-P``
> +
> +        Set the ethdev ports in promiscuous mode.
> +
> +* ``--config <config>``
> +
> +        Set the Rxq configuration.
> +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> +
> +* ``--node-pattern <n>``
> +
> +        Set the node patterns to use in graph creation.
> +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> +
> +* ``--per-port-pool``
> +
> +        Use separate buffer pool per port.
> +
> +* ``--no-numa``
> +
> +        Disable numa awareness.
> +
> +* ``--interactive``
> +
> +        Switch to interactive mode.
> +
> +Running the Tool
> +~~~~~~~~~~~~~~~~
> +
> +Here is the sample command line to run simple iofwd test::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> +
> +Below is a sample command line to punt rx packets to kernel::
> +
> +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> +
> +Interactive mode
> +~~~~~~~~~~~~~~~~
> +
> +Tool uses ``--interactive`` command line option to enter interactive mode and
> use cmdline options
> +to setup the required node configurations, create graph and than start
> graph_walk.
> +
> +
> +testgraph> help
> +
> +Help is available for the following sections:
> +
> +    help control                    : Start and stop graph walk.
> +    help display                    : Displaying port, stats and config information.
> +    help config                     : Configuration information.
> +    help all                        : All of the above sections.
> +
> +testgraph> help all
> +
> +Control forwarding:
> +
> +start graph_walk
> + Start graph_walk on worker threads.
> +
> +stop graph_walk
> + Stop worker threads from running graph_walk.
> +
> +quit
> + Quit to prompt.
> +
> +
> +Display:
> +
> +show node_list
> + Display the list of supported nodes.
> +
> +show graph_stats
> + Display the node statistics of graph cluster.
> +
> +
> +Configuration:
> +
> +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> + Set lcore configuration.
> +
> +create_graph (node0_name,node1_name,...,nodeX_name)
> + Create graph instances using the provided node details.
> +
> +destroy_graph
> + Destroy the graph instances.
> +
> +testgraph>
> --
> 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [PATCH v2 4/4] app: add testgraph application
  2023-05-12 11:08     ` Yan, Zhirun
@ 2023-05-22  7:07       ` Vamsi Krishna Attunuru
  2023-05-30  7:34         ` Jerin Jacob
  0 siblings, 1 reply; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-05-22  7:07 UTC (permalink / raw)
  To: Yan, Zhirun, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Nithin Kumar Dabilpuram, Liang, Cunming, Wang, Haiyue, Sunil Kumar Kori

Thanks Yan for the review comments.

> -----Original Message-----
> From: Yan, Zhirun <zhirun.yan@intel.com>
> Sent: Friday, May 12, 2023 4:38 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>; Sunil
> Kumar Kori <skori@marvell.com>
> Subject: [EXT] RE: [PATCH v2 4/4] app: add testgraph application
> 
> External Email
> 
> ----------------------------------------------------------------------
> 
> 
> > -----Original Message-----
> > From: Vamsi Attunuru <vattunuru@marvell.com>
> > Sent: Tuesday, April 25, 2023 9:15 PM
> > To: dev@dpdk.org; thomas@monjalon.net; jerinj@marvell.com
> > Cc: vattunuru@marvell.com; ndabilpuram@marvell.com
> > Subject: [PATCH v2 4/4] app: add testgraph application
> >
> > Patch adds test-graph application to validate graph
> > and node libraries.
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
> >  app/meson.build                |    1 +
> >  app/test-graph/cmdline.c       |  211 +++++
> >  app/test-graph/cmdline_graph.c |  294 +++++++
> >  app/test-graph/cmdline_graph.h |   19 +
> >  app/test-graph/meson.build     |   14 +
> >  app/test-graph/parameters.c    |  157 ++++
> >  app/test-graph/testgraph.c     | 1426
> ++++++++++++++++++++++++++++++++
> >  app/test-graph/testgraph.h     |   91 ++
> >  doc/guides/tools/index.rst     |    1 +
> >  doc/guides/tools/testgraph.rst |  131 +++
> >  10 files changed, 2345 insertions(+)
> >
> > diff --git a/app/meson.build b/app/meson.build
> > index 74d2420f67..6c7b24e604 100644
> > --- a/app/meson.build
> > +++ b/app/meson.build
> > @@ -22,6 +22,7 @@ apps = [
> >          'test-eventdev',
> >          'test-fib',
> >          'test-flow-perf',
> > +        'test-graph',
> >          'test-gpudev',
> >          'test-mldev',
> >          'test-pipeline',
> > diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> > new file mode 100644
> > index 0000000000..d9474d827a
> > --- /dev/null
> > +++ b/app/test-graph/cmdline.c
> > @@ -0,0 +1,211 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <stdlib.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_etheraddr.h>
> > +#include <cmdline_parse_ipaddr.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +#include <cmdline_rdline.h>
> > +#include <cmdline_socket.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +static struct cmdline *testgraph_cl;
> > +static cmdline_parse_ctx_t *main_ctx;
> > +
> > +/* *** Help command with introduction. *** */
> > +struct cmd_help_brief_result {
> > +	cmdline_fixed_string_t help;
> > +};
> > +
> > +static void
> > +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct
> cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	cmdline_printf(cl,
> > +		       "\n"
> > +		       "Help is available for the following sections:\n\n"
> > +		       "    help control                    : Start and stop graph walk.\n"
> > +		       "    help display                    : Displaying port, stats and config
> > "
> > +		       "information.\n"
> > +		       "    help config                     : Configuration information.\n"
> > +		       "    help all                        : All of the above sections.\n\n");
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_brief_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help,
> "help");
> > +
> > +static cmdline_parse_inst_t cmd_help_brief = {
> > +	.f = cmd_help_brief_parsed,
> > +	.data = NULL,
> > +	.help_str = "help: Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_brief_help,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Help command with help sections. *** */
> > +struct cmd_help_long_result {
> > +	cmdline_fixed_string_t help;
> > +	cmdline_fixed_string_t section;
> > +};
> > +
> > +static void
> > +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl,
> __rte_unused
> > void *data)
> > +{
> > +	int show_all = 0;
> > +	struct cmd_help_long_result *res = parsed_result;
> > +
> > +	if (!strcmp(res->section, "all"))
> > +		show_all = 1;
> > +
> > +	if (show_all || !strcmp(res->section, "control")) {
> > +
> > +		cmdline_printf(cl, "\n"
> > +				   "Control forwarding:\n"
> > +				   "-------------------\n\n"
> > +
> > +				   "start graph_walk\n"
> > +				   " Start graph_walk on worker threads.\n\n"
> > +
> > +				   "stop graph_walk\n"
> > +				   " Stop worker threads from running
> > graph_walk.\n\n"
> > +
> > +				   "quit\n"
> > +				   "    Quit to prompt.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "display")) {
> > +
> > +		cmdline_printf(cl,
> > +			       "\n"
> > +			       "Display:\n"
> > +			       "--------\n\n"
> > +
> > +			       "show node_list\n"
> > +			       " Display the list of supported nodes.\n\n"
> > +
> > +			       "show graph_stats\n"
> > +			       " Display the node statistics of graph
> cluster.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "config")) {
> > +		cmdline_printf(cl, "\n"
> > +				   "Configuration:\n"
> > +				   "--------------\n"
> > +				   "set lcore_config
> > (port_id0,rxq0,lcore_idX),..."
> > +				   ".....,(port_idX,rxqX,lcoreidY)\n"
> > +				   " Set lcore configuration.\n\n"
> > +
> > +				   "create_graph
> > (node0_name,node1_name,...,nodeX_name)\n"
> > +				   " Create graph instances using the provided
> > node details.\n\n"
> > +
> > +				   "destroy_graph\n"
> > +				   " Destroy the graph instances.\n\n");
> > +	}
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help,
> "help");
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_section =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_help_long_result, section, "all#control#display#config");
> > +
> > +static cmdline_parse_inst_t cmd_help_long = {
> > +	.f = cmd_help_long_parsed,
> > +	.data = NULL,
> > +	.help_str = "help all|control|display|config: "
> > +		    "Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_long_help,
> > +			(void *)&cmd_help_long_section,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** QUIT *** */
> > +struct cmd_quit_result {
> > +	cmdline_fixed_string_t quit;
> > +};
> > +
> > +static void
> > +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	cmdline_quit(cl);
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_quit_quit =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> > +
> > +static cmdline_parse_inst_t cmd_quit = {
> > +	.f = cmd_quit_parsed,
> > +	.data = NULL,
> > +	.help_str = "quit: Exit application",
> > +	.tokens = {
> > +			(void *)&cmd_quit_quit,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* list of instructions */
> > +static cmdline_parse_ctx_t builtin_ctx[] = {
> > +	(cmdline_parse_inst_t *)&cmd_help_brief,
> > +	(cmdline_parse_inst_t *)&cmd_help_long,
> > +	(cmdline_parse_inst_t *)&cmd_quit,
> > +	(cmdline_parse_inst_t *)&cmd_show_node_list,
> > +	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
> > +	(cmdline_parse_inst_t *)&cmd_create_graph,
> > +	(cmdline_parse_inst_t *)&cmd_destroy_graph,
> > +	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
> > +	NULL,
> > +};
> > +
> > +int
> > +init_cmdline(void)
> > +{
> > +	unsigned int count;
> > +	unsigned int i;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++)
> > +		count++;
> > +
> > +	/* cmdline expects a NULL terminated array */
> > +	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> > +	if (main_ctx == NULL)
> > +		return -1;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> > +		main_ctx[count] = builtin_ctx[i];
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +prompt_exit(void)
> > +{
> > +	cmdline_quit(testgraph_cl);
> > +}
> > +
> > +/* prompt function, called from main on MAIN lcore */
> > +void
> > +prompt(void)
> > +{
> > +	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> > +	if (testgraph_cl == NULL) {
> > +		fprintf(stderr, "Failed to create stdin based cmdline
> context\n");
> > +		return;
> > +	}
> > +
> > +	cmdline_interact(testgraph_cl);
> > +	cmdline_stdin_exit(testgraph_cl);
> > +}
> > diff --git a/app/test-graph/cmdline_graph.c b/app/test-
> graph/cmdline_graph.c
> > new file mode 100644
> > index 0000000000..d66149a224
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.c
> > @@ -0,0 +1,294 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <stdlib.h>
> > +#include <string.h>
> > +
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +/* *** Show supported node details *** */
> > +struct cmd_show_node_list_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t node_list;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_node_list_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> show,
> > "show");
> > +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> > node_list, "node_list");
> > +
> > +static void
> > +cmd_show_node_list_parsed(__rte_unused void *parsed_result,
> __rte_unused
> > struct cmdline *cl,
> > +			  __rte_unused void *data)
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	rte_node_t id;
> > +
> > +	printf("\n**** Supported Graph Nodes ****\n");
> > +	for (id = 0; id < node_cnt; id++)
> > +		printf("%s\n", rte_node_id_to_name(id));
> > +
> > +	printf("********************************\n");
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_node_list = {
> > +	.f = cmd_show_node_list_parsed,
> > +	.data = NULL,
> > +	.help_str = "show node_list",
> > +	.tokens = {
> > +			(void *)&cmd_show_node_list_show,
> > +			(void *)&cmd_show_node_list_node_list,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Set lcore config *** */
> > +struct cmd_set_lcore_config_result {
> > +	cmdline_fixed_string_t set;
> > +	cmdline_fixed_string_t lcore_config;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> set,
> > "set");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> > lcore_config, "lcore_config");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string
> =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_set_lcore_config_result, token_string,
> > TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct
> > cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	struct cmd_set_lcore_config_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	int ret;
> > +
> > +	/* Parse string */
> > +	ret = parse_config(t_str);
> > +	if (ret) {
> > +		printf(" lcore_config string parse error\n");
> > +		return;
> > +	}
> > +
> > +	validate_config();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_set_lcore_config = {
> > +	.f = cmd_set_lcore_config_parsed,
> > +	.data = NULL,
> > +	.help_str = "set lcore_config "
> > +		    "(port,queue,lcore),[(port,queue,lcore) ...
> > (port,queue,lcore)]",
> > +	.tokens = {
> > +			(void *)&cmd_set_lcore_config_set,
> > +			(void *)&cmd_set_lcore_config_lcore_config,
> > +			(void *)&cmd_set_lcore_config_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Create graph *** */
> > +struct cmd_create_graph_result {
> > +	cmdline_fixed_string_t create_graph;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > create_graph, "create_graph");
> > +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > token_string, TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct
> cmdline
> > *cl,
> > +			__rte_unused void *data)
> > +{
> > +	struct cmd_create_graph_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	uint64_t valid_nodes = 0;
> > +	int ret;
> > +
> > +	ret = parse_node_patterns(t_str);
> > +	if (ret) {
> > +		printf("parse_node_patterns failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> > +
> > +	ret = validate_node_names(&valid_nodes);
> > +	if (ret) {
> > +		printf("validate_node_names() failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> 
> Hi Vamsi,
> 
> The global node_pattern should be cleaned in destroy stage or make sure it
> populated from cmd line completely.
> In Interactive-mode, user may create and destroy a graph and then create a
> new one.
> For this case, the node name is changed by the previous creation, especially
> for rx/tx node.(ethdev_rx -> ethdev_rx-0-0)
> 
Sure, will fix it next version.

> > +
> > +	nb_conf = ethdev_ports_setup();
> > +
> > +	ethdev_rxq_configure();
> > +	ethdev_txq_configure();
> > +
> > +	ret = configure_graph_nodes(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> err=%d\n",
> > ret);
> > +
> > +	ret = create_graph(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> > +
> > +	stats = create_graph_cluster_stats();
> > +	if (stats == NULL)
> > +		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> failed\n");
> > +
> > +	check_all_ports_link_status(enabled_port_mask);
> > +	start_eth_ports();
> > +}
> 
> Could this create cmd be break down for graph_config and graph_create?
> In graph_config stage, we can set/verify graph topo, chose graph-walk
> models and update node ctx, etc.
> 

How about having a separate cmd to select the graph-walk model, that could be
run before create_graph cmd. Yes it could be split if really required, let me check
what all can be moved out of graph_create().

> > +
> > +cmdline_parse_inst_t cmd_create_graph = {
> > +	.f = cmd_create_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "create_graph "
> > +		    "[node_name0,node_name1,node_name2 ...
> node_nameX]",
> > +	.tokens = {
> > +			(void *)&cmd_create_graph_create_graph,
> > +			(void *)&cmd_create_graph_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Destroy graph ****/
> > +struct cmd_destroy_graph_result {
> > +	cmdline_fixed_string_t destroy_graph;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result,
> > destroy_graph, "destroy_graph");
> > +
> > +static void
> > +cmd_destroy_graph_parsed(__rte_unused void *parsed_result,
> __rte_unused
> > struct cmdline *cl,
> > +			 __rte_unused void *data)
> > +{
> > +	uint32_t lcore_id;
> > +
> > +	run_graph_walk = false;
> > +	graph_walk_quit = true;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +		rte_eal_wait_lcore(lcore_id);
> > +
> > +	destroy_graph();
> > +	stop_eth_ports();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_destroy_graph = {
> > +	.f = cmd_destroy_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "destroy_graph",
> > +	.tokens = {
> > +			(void *)&cmd_destroy_graph_destroy_graph,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Start graph_walk ****/
> > +struct cmd_start_graph_walk_result {
> > +	cmdline_fixed_string_t start;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> start,
> > "start");
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	static bool launch_graph_walk;
> > +
> > +	if (!launch_graph_walk) {
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> 
> As run_graph_walk is used to control graph-walk, Why not put the launch in
> create stage?

Yes, will change in next version.

> 
> > +		launch_graph_walk = true;
> > +	}
> > +
> > +	run_graph_walk = true;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_start_graph_walk = {
> > +	.f = cmd_start_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "start graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_start_graph_walk_start,
> > +			(void *)&cmd_start_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Stop graph_walk ****/
> > +struct cmd_stop_graph_walk_result {
> > +	cmdline_fixed_string_t stop;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> stop,
> > "stop");
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			   __rte_unused void *data)
> > +{
> > +	run_graph_walk = false;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_stop_graph_walk = {
> > +	.f = cmd_stop_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "stop graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_stop_graph_walk_stop,
> > +			(void *)&cmd_stop_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Show graph_stats ****/
> > +struct cmd_show_graph_stats_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t graph_stats;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > show, "show");
> > +static cmdline_parse_token_string_t
> cmd_show_graph_stats_graph_stats =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > graph_stats, "graph_stats");
> > +
> > +static void
> > +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	if (rte_graph_has_stats_feature()) {
> > +		if (stats)
> > +			rte_graph_cluster_stats_get(stats, 0);
> > +	} else {
> > +		printf(" graph stats feature not enabled in rte_config.\n");
> > +	}
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_graph_stats = {
> > +	.f = cmd_show_graph_stats_parsed,
> > +	.data = NULL,
> > +	.help_str = "show graph_stats",
> > +	.tokens = {
> > +			(void *)&cmd_show_graph_stats_show,
> > +			(void *)&cmd_show_graph_stats_graph_stats,
> > +			NULL,
> > +		},
> > +};
> > diff --git a/app/test-graph/cmdline_graph.h b/app/test-
> graph/cmdline_graph.h
> > new file mode 100644
> > index 0000000000..2846ff5425
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _CMDLINE_GRAPH_H_
> > +#define _CMDLINE_GRAPH_H_
> > +
> > +extern cmdline_parse_inst_t cmd_show_node_list;
> > +extern cmdline_parse_inst_t cmd_set_lcore_config;
> > +
> > +extern cmdline_parse_inst_t cmd_create_graph;
> > +extern cmdline_parse_inst_t cmd_destroy_graph;
> > +
> > +extern cmdline_parse_inst_t cmd_start_graph_walk;
> > +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> > +
> > +extern cmdline_parse_inst_t cmd_show_graph_stats;
> > +
> > +#endif /* _CMDLINE_GRAPH_H_ */
> > diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> > new file mode 100644
> > index 0000000000..d93802a975
> > --- /dev/null
> > +++ b/app/test-graph/meson.build
> > @@ -0,0 +1,14 @@
> > +# SPDX-License-Identifier: BSD-3-Clause
> > +# Copyright(C) 2023 Marvell International Ltd.
> > +
> > +# override default name to drop the hyphen
> > +name = 'test-graph'
> > +cflags += '-Wno-deprecated-declarations'
> > +sources = files(
> > +        'cmdline.c',
> > +        'cmdline_graph.c',
> > +        'parameters.c',
> > +        'testgraph.c',
> > +)
> > +
> > +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> > diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> > new file mode 100644
> > index 0000000000..b990ca4a1c
> > --- /dev/null
> > +++ b/app/test-graph/parameters.c
> > @@ -0,0 +1,157 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +
> > +#include "testgraph.h"
> > +
> > +static const char short_options[] = "p:" /* portmask */
> > +				    "P"	 /* promiscuous */
> > +				    "i"	 /* interactive */
> > +	;
> > +
> > +#define CMD_LINE_OPT_CONFIG	   "config"
> > +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> > +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> > +#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
> > +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> > +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_CONFIG_NUM,
> > +	CMD_LINE_OPT_NODE_PATTERN_NUM,
> > +	CMD_LINE_OPT_INTERACTIVE_NUM,
> > +	CMD_LINE_OPT_NO_NUMA_NUM,
> > +	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> > +};
> > +
> > +static const struct option lgopts[] = {
> > +	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> > +	{CMD_LINE_OPT_NODE_PATTERN, 1, 0,
> > CMD_LINE_OPT_NODE_PATTERN_NUM},
> > +	{CMD_LINE_OPT_INTERACTIVE, 0, 0,
> > CMD_LINE_OPT_INTERACTIVE_NUM},
> > +	{CMD_LINE_OPT_NO_NUMA, 0, 0,
> CMD_LINE_OPT_NO_NUMA_NUM},
> > +	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0,
> > CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> > +	{NULL, 0, 0, 0},
> > +};
> > +
> > +/* Display usage */
> > +static void
> > +print_usage(const char *prgname)
> > +{
> > +	fprintf(stderr,
> > +		"%s [EAL options] --"
> > +		" -p PORTMASK"
> > +		" [-P]"
> > +		" [-i]"
> > +		" --config (port,queue,lcore)[,(port,queue,lcore)]"
> > +		" --node-pattern
> (node_name0,node_name1[,node_nameX)]"
> > +		" [--no-numa]"
> > +		" [--per-port-pool]"
> > +		" [--interactive]"
> > +
> > +		"  -p PORTMASK: Hexadecimal bitmask of ports to
> configure\n"
> > +		"  -P : Enable promiscuous mode\n"
> > +		"  -i : Enter interactive mode\n"
> > +		"  --config (port,queue,lcore): Rx queue configuration\n"
> > +		"  --node-pattern (node_names): node patterns to create
> > graph\n"
> > +		"  --no-numa: Disable numa awareness\n"
> > +		"  --per-port-pool: Use separate buffer pool per port\n"
> > +		"  --interactive: Enter interactive mode\n",
> > +		prgname);
> > +}
> > +
> > +static int
> > +parse_portmask(const char *portmask)
> > +{
> > +	char *end = NULL;
> > +	unsigned long pm;
> > +
> > +	/* Parse hexadecimal string */
> > +	pm = strtoul(portmask, &end, 16);
> > +	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> > +		return 0;
> > +
> > +	return pm;
> > +}
> > +
> > +/* Parse the argument given in the command line of the application */
> > +int
> > +parse_cmdline_args(int argc, char **argv)
> > +{
> > +	char *prgname = argv[0];
> > +	int option_index;
> > +	char **argvopt;
> > +	int opt, ret;
> > +
> > +	argvopt = argv;
> > +
> > +	/* Error or normal output strings. */
> > +	while ((opt = getopt_long(argc, argvopt, short_options, lgopts,
> > &option_index)) != EOF) {
> > +
> > +		switch (opt) {
> > +		/* Portmask */
> > +		case 'p':
> > +			enabled_port_mask = parse_portmask(optarg);
> > +			if (enabled_port_mask == 0) {
> > +				fprintf(stderr, "Invalid portmask\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case 'P':
> > +			promiscuous_on = 1;
> > +			break;
> > +
> > +		/* Long options */
> > +		case CMD_LINE_OPT_CONFIG_NUM:
> > +			ret = parse_config(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid config\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +		case CMD_LINE_OPT_NODE_PATTERN_NUM:
> > +			ret = parse_node_patterns(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid node_patterns\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case CMD_LINE_OPT_INTERACTIVE_NUM:
> > +		case 'i':
> > +			printf("Interactive-mode selected\n");
> > +			interactive = 1;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_NO_NUMA_NUM:
> > +			numa_on = 0;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> > +			printf("Per port buffer pool is enabled\n");
> > +			per_port_pool = 1;
> > +			break;
> > +
> > +		default:
> > +			print_usage(prgname);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	if (optind >= 0)
> > +		argv[optind - 1] = prgname;
> > +	ret = optind - 1;
> > +	optind = 1; /* Reset getopt lib */
> > +
> > +	return ret;
> > +}
> > diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> > new file mode 100644
> > index 0000000000..aff921acf2
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.c
> > @@ -0,0 +1,1426 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <fnmatch.h>
> > +#include <signal.h>
> > +#include <stdlib.h>
> > +
> > +#include <rte_bus.h>
> > +#include <rte_byteorder.h>
> > +#include <rte_common.h>
> > +#include <rte_dev.h>
> > +#include <rte_eal.h>
> > +#include <rte_ethdev.h>
> > +#include <rte_ether.h>
> > +#include <rte_graph_worker.h>
> > +#include <rte_launch.h>
> > +#include <rte_lcore.h>
> > +#include <rte_log.h>
> > +#include <rte_malloc.h>
> > +#include <rte_mempool.h>
> > +#include <rte_per_lcore.h>
> > +#include <ethdev_rx_priv.h>
> > +#include <ethdev_tx_priv.h>
> > +#include <punt_kernel_priv.h>
> > +#include <kernel_recv_priv.h>
> > +
> > +#include "testgraph.h"
> > +
> > +/* Log type */
> > +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> > +
> > +/*
> > + * Configurable number of RX/TX ring descriptors
> > + */
> > +#define RX_DESC_DEFAULT 1024
> > +#define TX_DESC_DEFAULT 1024
> > +
> > +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> > +#define MAX_RX_QUEUE_PER_PORT 128
> > +
> > +#define MAX_RX_QUEUE_PER_LCORE 16
> > +
> > +#define NB_SOCKETS 8
> > +
> > +/* Static global variables used within this file. */
> > +uint16_t nb_rxd = RX_DESC_DEFAULT;
> > +uint16_t nb_txd = TX_DESC_DEFAULT;
> > +
> > +static volatile bool force_quit;
> > +volatile bool graph_walk_quit;
> > +volatile bool run_graph_walk = true;
> > +
> > +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] =
> {0};
> > +const char **node_patterns;
> > +uint8_t num_patterns;
> > +
> > +uint8_t interactive; /**< interactive mode is off by default */
> > +int promiscuous_on; /**< Ports set in promiscuous mode off by default.
> */
> > +int numa_on = 1;   /**< NUMA is enabled by default. */
> > +int per_port_pool; /**< Use separate buffer pools per port, disabled by
> default
> > */
> > +int testgraph_logtype; /**< Log type for testgraph logs */
> > +
> > +struct rte_graph_cluster_stats *stats;
> > +
> > +uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +
> > +uint32_t nb_conf;
> > +
> > +struct lcore_rx_queue {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	char node_name[RTE_NODE_NAMESIZE];
> > +};
> > +
> > +/* Lcore conf */
> > +struct lcore_conf {
> > +	uint16_t n_rx_queue;
> > +	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> > +	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> > +	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
> > +
> > +	struct rte_graph *graph;
> > +	char name[RTE_GRAPH_NAMESIZE];
> > +	rte_graph_t graph_id;
> > +} __rte_cache_aligned;
> > +
> > +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> > +
> > +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> > +static struct lcore_params lcore_params_array_default[] = {
> > +	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> > +	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> > +};
> > +
> > +struct lcore_params *lcore_params = lcore_params_array_default;
> > +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +static struct rte_eth_conf port_conf = {
> > +	.rxmode = {
> > +			.mq_mode = RTE_ETH_MQ_RX_RSS,
> > +		},
> > +	.rx_adv_conf = {
> > +			.rss_conf = {
> > +					.rss_key = NULL,
> > +					.rss_hf = RTE_ETH_RSS_IP,
> > +				},
> > +		},
> > +	.txmode = {
> > +			.mq_mode = RTE_ETH_MQ_TX_NONE,
> > +		},
> > +};
> > +
> > +static const struct node_list test_node_list[] = {{{"ethdev_rx",
> "ethdev_tx"}, 0,
> > 2},
> > +						{{"ethdev_rx",
> "punt_kernel"},
> > 0, 2},
> > +						{{"kernel_recv",
> "ethdev_tx"},
> > 0, 2} };
> > +
> 
> Here we support 3 topo, could you support the original l3fwd nodes first?

Sure, firstly started with simple nodes which are quite independent and does not
require additional configurations like lpm, rewrite etc.

> 
> > +static const struct node_list supported_nodes[] = {{{"ethdev_rx"},
> > TEST_GRAPH_ETHDEV_RX_NODE, 1},
> > +						{{"ethdev_tx"},
> > TEST_GRAPH_ETHDEV_TX_NODE, 1},
> > +						{{"punt_kernel"},
> > TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> > +						{{"kernel_recv"},
> > TEST_GRAPH_KERNEL_RECV_NODE, 1},
> > +						{{"ip4_lookup"},
> > TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> > +						{{"ip4_rewrite"},
> > TEST_GRAPH_IP4_REWRITE_NODE, 1},
> > +						{{"pkt_cls"},
> > TEST_GRAPH_PKT_CLS_NODE, 1},
> > +						{{"pkt_drop"},
> > TEST_GRAPH_PKT_DROP_NODE, 1},
> > +						{{"NULL"},
> > TEST_GRAPH_NULL_NODE, 1} };
> > +
> > +static struct rte_mempool
> > *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> > +
> > +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> > +
> > +static int
> > +check_lcore_params(void)
> > +{
> > +	uint8_t queue, lcore;
> > +	int socketid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		queue = lcore_params[i].queue_id;
> > +		if (queue >= MAX_RX_QUEUE_PER_PORT) {
> > +			printf("Invalid queue number: %hhu\n", queue);
> > +			return -1;
> > +		}
> > +		lcore = lcore_params[i].lcore_id;
> > +		if (!rte_lcore_is_enabled(lcore)) {
> > +			printf("Error: lcore %hhu is not enabled in lcore
> mask\n",
> > lcore);
> > +			return -1;
> > +		}
> > +
> > +		if (lcore == rte_get_main_lcore()) {
> > +			printf("Error: lcore %u is main lcore\n", lcore);
> > +			return -1;
> > +		}
> > +		socketid = rte_lcore_to_socket_id(lcore);
> > +		if ((socketid != 0) && (numa_on == 0)) {
> > +			printf("Warning: lcore %hhu is on socket %d with
> numa
> > off\n", lcore,
> > +			       socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +check_port_config(void)
> > +{
> > +	uint16_t portid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		portid = lcore_params[i].port_id;
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("Port %u is not enabled in port mask\n",
> portid);
> > +			return -1;
> > +		}
> > +		if (!rte_eth_dev_is_valid_port(portid)) {
> > +			printf("Port %u is not present on the board\n",
> portid);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static uint8_t
> > +get_port_n_rx_queues(const uint16_t port)
> > +{
> > +	int queue = -1;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		if (lcore_params[i].port_id == port) {
> > +			if (lcore_params[i].queue_id == queue + 1)
> > +				queue = lcore_params[i].queue_id;
> > +			else
> > +				rte_exit(EXIT_FAILURE, "Queue ids of the
> > port %d must be"
> > +					 " in sequence and must start with
> 0\n",
> > +					 lcore_params[i].port_id);
> > +		}
> > +	}
> > +
> > +	return (uint8_t)(++queue);
> > +}
> > +
> > +static int
> > +init_lcore_rx_queues(void)
> > +{
> > +	uint16_t i, nb_rx_queue;
> > +	uint8_t lcore;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		lcore = lcore_params[i].lcore_id;
> > +		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> > +		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> > +			printf("Error: too many queues (%u) for lcore: %u\n",
> > +			       (unsigned int)nb_rx_queue + 1, (unsigned
> int)lcore);
> > +			return -1;
> > +		}
> > +
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id =
> > lcore_params[i].port_id;
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id =
> > lcore_params[i].queue_id;
> > +		lcore_conf[lcore].n_rx_queue++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +validate_config(void)
> > +{
> > +	int rc = -1;
> > +
> > +	if (check_lcore_params() < 0) {
> > +		printf("check_lcore_params() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (init_lcore_rx_queues() < 0) {
> > +		printf("init_lcore_rx_queues() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (check_port_config() < 0) {
> > +		printf("check_port_config() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +
> > +exit:
> > +	return rc;
> > +}
> > +
> > +#define MEMPOOL_CACHE_SIZE 256
> > +
> > +/*
> > + * This expression is used to calculate the number of mbufs needed
> > + * depending on user input, taking  into account memory for rx and
> > + * tx hardware rings, cache per lcore and mtable per port per lcore.
> > + * RTE_MAX is used to ensure that NB_MBUF never goes below a
> minimum
> > + * value of 8192
> > + */
> > +#define NB_MBUF(nports)                                                                            \
> > +	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores *
> > RTE_GRAPH_BURST_SIZE +       \
> > +		 nports * nb_tx_queue * nb_txd + nb_lcores *
> > MEMPOOL_CACHE_SIZE),                  \
> > +		8192u)
> > +
> > +static int
> > +init_mem(uint16_t portid, uint32_t nb_mbuf)
> > +{
> > +	uint32_t lcore_id;
> > +	int socketid;
> > +	char s[64];
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		if (numa_on)
> > +			socketid = rte_lcore_to_socket_id(lcore_id);
> > +		else
> > +			socketid = 0;
> > +
> > +		if (socketid >= NB_SOCKETS) {
> > +			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out
> of
> > range %d\n",
> > +				 socketid, lcore_id, NB_SOCKETS);
> > +		}
> > +
> > +		if (pktmbuf_pool[portid][socketid] == NULL) {
> > +			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid,
> > socketid);
> > +			/* Create a pool with priv size of a cacheline */
> > +			pktmbuf_pool[portid][socketid] =
> > rte_pktmbuf_pool_create(
> > +				s, nb_mbuf, MEMPOOL_CACHE_SIZE,
> > RTE_CACHE_LINE_SIZE,
> > +				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> > +			if (pktmbuf_pool[portid][socketid] == NULL)
> > +				rte_exit(EXIT_FAILURE, "Cannot init mbuf
> pool
> > on socket %d\n",
> > +					 socketid);
> > +			else
> > +				printf("Allocated mbuf pool on socket %d\n",
> > socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +check_all_ports_link_status(uint32_t port_mask)
> > +{
> > +#define CHECK_INTERVAL 100 /* 100ms */
> > +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> > +	uint8_t count, all_ports_up, print_flag = 0;
> > +	struct rte_eth_link link;
> > +	uint16_t portid;
> > +	int ret;
> > +	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> > +
> > +	printf("\nChecking link status");
> > +	fflush(stdout);
> > +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> > +		if (force_quit)
> > +			return;
> > +		all_ports_up = 1;
> > +		RTE_ETH_FOREACH_DEV(portid) {
> > +			if (force_quit)
> > +				return;
> > +			if ((port_mask & (1 << portid)) == 0)
> > +				continue;
> > +			memset(&link, 0, sizeof(link));
> > +			ret = rte_eth_link_get_nowait(portid, &link);
> > +			if (ret < 0) {
> > +				all_ports_up = 0;
> > +				if (print_flag == 1)
> > +					printf("Port %u link get failed: %s\n",
> > portid,
> > +					       rte_strerror(-ret));
> > +				continue;
> > +			}
> > +			/* Print link status if flag set */
> > +			if (print_flag == 1) {
> > +				rte_eth_link_to_str(link_status_text,
> > sizeof(link_status_text),
> > +						    &link);
> > +				printf("Port %d %s\n", portid,
> link_status_text);
> > +				continue;
> > +			}
> > +			/* Clear all_ports_up flag if any link down */
> > +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> > +				all_ports_up = 0;
> > +				break;
> > +			}
> > +		}
> > +		/* After finally printing all link status, get out */
> > +		if (print_flag == 1)
> > +			break;
> > +
> > +		if (all_ports_up == 0) {
> > +			printf(".");
> > +			fflush(stdout);
> > +			rte_delay_ms(CHECK_INTERVAL);
> > +		}
> > +
> > +		/* Set the print_flag if all ports up or timeout */
> > +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> > +			print_flag = 1;
> > +			printf("Done\n");
> > +		}
> > +	}
> > +}
> > +
> > +static void
> > +signal_handler(int signum)
> > +{
> > +	if (signum == SIGINT || signum == SIGTERM) {
> > +		printf("\n\nSignal %d received, preparing to exit...\n",
> signum);
> > +		force_quit = true;
> > +	}
> > +	prompt_exit();
> > +}
> > +
> > +int
> > +graph_main_loop(void *conf)
> > +{
> > +	struct lcore_conf *qconf;
> > +	struct rte_graph *graph;
> > +	uint32_t lcore_id;
> > +
> > +	RTE_SET_USED(conf);
> > +
> > +	lcore_id = rte_lcore_id();
> > +	qconf = &lcore_conf[lcore_id];
> > +	graph = qconf->graph;
> > +
> > +	if (!graph) {
> > +		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to
> do\n",
> > lcore_id);
> > +		return 0;
> > +	}
> > +
> > +	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u,
> > graph %s(%p)\n", lcore_id,
> > +		qconf->name, graph);
> > +
> > +	while (likely(!force_quit && !graph_walk_quit)) {
> > +		if (likely(run_graph_walk))
> > +			rte_graph_walk(graph);
> > +	}
> 
> graph_walk_quit could be changed to true in destroy stage,  we should set it
> false in create stage also.
> Otherwise, it will walk only once for interactive-mode.
> 
> I am agree to use different switch for different stage to start/stop,
> create/destroy and termination.
> force_quit + graph_walk_quit + run_graph_walk looks little overcomplicated.
> Could it only use
> force_quit to pause the walk in worker?

Sure, will simplify the path.

> 
> > +
> > +	return 0;
> > +}
> > +
> > +struct rte_graph_cluster_stats *
> > +create_graph_cluster_stats(void)
> > +{
> > +	struct rte_graph_cluster_stats_param s_param;
> > +	struct rte_graph_cluster_stats *stat;
> > +	const char *pattern = "worker_*";
> > +
> > +	/* 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;
> > +
> > +	stat = rte_graph_cluster_stats_create(&s_param);
> > +	if (stat == NULL)
> > +		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> > +
> > +	return stat;
> > +}
> > +
> > +static void
> > +print_stats(void)
> > +{
> > +	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> > +	const char clr[] = {27, '[', '2', 'J', '\0'};
> > +
> > +	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);
> > +}
> > +
> > +int
> > +parse_config(const char *q_arg)
> > +{
> > +	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE,
> > _NUM_FLD };
> > +	unsigned long int_fld[_NUM_FLD];
> > +	const char *p, *p0 = q_arg;
> > +	char *str_fld[_NUM_FLD];
> > +	uint32_t size;
> > +	char s[256];
> > +	char *end;
> > +	int i;
> > +
> > +	nb_lcore_params = 0;
> > +
> > +	while ((p = strchr(p0, '(')) != NULL) {
> > +		++p;
> > +		p0 = strchr(p, ')');
> > +		if (p0 == NULL)
> > +			goto exit;
> > +
> > +		size = p0 - p;
> > +		if (size >= sizeof(s))
> > +			goto exit;
> > +
> > +		memcpy(s, p, size);
> > +		s[size] = '\0';
> > +		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') !=
> _NUM_FLD)
> > +			goto exit;
> > +		for (i = 0; i < _NUM_FLD; i++) {
> > +			errno = 0;
> > +			int_fld[i] = strtoul(str_fld[i], &end, 0);
> > +			if (errno != 0 || end == str_fld[i])
> > +				goto exit;
> > +		}
> > +
> > +		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> > +			printf("Exceeded max number of lcore params:
> %hu\n",
> > nb_lcore_params);
> > +			goto exit;
> > +		}
> > +
> > +		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS ||
> > int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> > +			printf("Invalid port/lcore id\n");
> > +			goto exit;
> > +		}
> > +
> > +		lcore_params_array[nb_lcore_params].port_id =
> > (uint8_t)int_fld[FLD_PORT];
> > +		lcore_params_array[nb_lcore_params].queue_id =
> > (uint8_t)int_fld[FLD_QUEUE];
> > +		lcore_params_array[nb_lcore_params].lcore_id =
> > (uint8_t)int_fld[FLD_LCORE];
> > +		++nb_lcore_params;
> > +	}
> > +	lcore_params = lcore_params_array;
> > +
> > +	return 0;
> > +exit:
> > +	/* Revert to default config */
> > +	lcore_params = lcore_params_array_default;
> > +	nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +	return -1;
> > +}
> > +
> > +int
> > +parse_node_patterns(const char *q_arg)
> > +{
> > +	const char *p, *p0 = q_arg;
> > +	int ret = -EINVAL;
> > +	uint32_t size;
> > +
> > +	num_patterns = 0;
> > +
> > +	p = strchr(p0, '(');
> > +	if (p != NULL) {
> > +		++p;
> > +		while ((p0 = strchr(p, ',')) != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> 
> Here, if not make node_pattern clean, node_pattern[0](size,MAX) will be
> tainted by previous create.

Ack. will fix it.
> 
> > +			p = p0 + 1;
> > +		}
> > +
> > +		p0 = strchr(p, ')');
> > +		if (p0 != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> > +		} else {
> > +			goto exit;
> > +		}
> > +	} else {
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static void
> > +set_default_node_pattern(void)
> > +{
> > +	uint16_t idx;
> > +
> > +	for (idx = 0; idx < test_node_list[0].size; idx++)
> > +		strcpy(node_pattern[num_patterns++],
> > test_node_list[0].nodes[idx]);
> > +}
> > +
> > +int
> > +validate_node_names(uint64_t *valid_nodes)
> 
> Actually, this func limits the node name to follow  test_node_list.
> It's OK if there are few nodes/edges.
> 
> But how to describe much nodes/edges like
> graph_dot {
>   A->B;
>   A->C;
>   B->D;
>   C->D;
> }
> 
> If update edges implicitly, there is no need to specific the node sequence.
> Both of (A,B,C,D) and (A,C,B,D) are correct.

Correct, will rework on interpreting /parsing it as expected by the topology.
> 
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	bool pattern_matched = false;
> > +	rte_node_t id = 0;
> > +	int ret = -EINVAL;
> > +	uint16_t idx, i, j;
> > +
> > +	for (idx = 0; idx < num_patterns; idx++) {
> > +		for (id = 0; id < node_cnt; id++) {
> > +			if (strncmp(node_pattern[idx],
> > rte_node_id_to_name(id),
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +				break;
> > +		}
> > +		if (node_cnt == id) {
> > +			printf("Invalid node name passed\n");
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +		idx = 0;
> > +		if (test_node_list[i].size == num_patterns) {
> > +			for (j = 0; j < num_patterns; j++) {
> > +				if (strncmp(node_pattern[j],
> > test_node_list[i].nodes[j],
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +					idx++;
> > +			}
> > +			if (idx == num_patterns)
> > +				pattern_matched = true;
> > +		}
> > +	}
> > +
> > +	if (!pattern_matched) {
> > +		printf("Unsupported node pattern passed\n\n");
> > +		printf("Test supported node patterns are:\n");
> > +		for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +			printf("(");
> > +			for (j = 0; j < (test_node_list[i].size - 1); j++)
> > +				printf("%s,", test_node_list[i].nodes[j]);
> > +			printf("%s", test_node_list[i].nodes[j]);
> > +			printf(")\n");
> > +		}
> > +
> > +		return ret;
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> > +		for (j = 0; j < num_patterns; j++) {
> > +			if (strncmp(node_pattern[j],
> > supported_nodes[i].nodes[0],
> > +				    RTE_GRAPH_NAMESIZE) == 0) {
> > +				*valid_nodes |= supported_nodes[i].test_id;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +cleanup_node_pattern(void)
> > +{
> > +	while (num_patterns) {
> > +		memset(node_pattern[num_patterns - 1], 0,
> > RTE_GRAPH_NAMESIZE);
> > +		num_patterns--;
> > +	}
> > +}
> > +
> > +static int
> > +ethdev_tx_node_configure(void)
> > +{
> > +	struct ethdev_tx_node_main *tx_node_data;
> > +	struct rte_node_register *tx_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t port_id;
> > +	uint32_t id;
> > +
> > +	tx_node_data = ethdev_tx_node_data_get();
> > +	tx_node = ethdev_tx_node_get();
> > +
> > +	RTE_ETH_FOREACH_DEV(port_id) {
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", port_id);
> > +			continue;
> > +		}
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create a per port tx node from base node */
> > +		snprintf(name, sizeof(name), "%u", port_id);
> > +		id = rte_node_clone(tx_node->id, name);
> > +		tx_node_data->nodes[port_id] = id;
> > +
> > +		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +punt_kernel_node_configure(void)
> > +{
> > +	struct rte_node_register *punt_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	punt_node = punt_kernel_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore punt_kernel node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(punt_node->id, name);
> > +		strcpy(qconf->punt_kernel_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("punt_kernel node %s-%s: is at %u\n", punt_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +kernel_recv_node_configure(void)
> > +{
> > +	struct rte_node_register *recv_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	recv_node = kernel_recv_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore kernel_recv node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(recv_node->id, name);
> > +		strcpy(qconf->kernel_recv_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("kernel_recv node %s-%s: is at %u\n", recv_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf,
> uint16_t
> > nb_confs)
> > +{
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t i, j, port_id;
> > +	uint32_t id;
> > +
> > +	for (i = 0; i < nb_confs; i++) {
> > +		port_id = conf[i].port_id;
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create node for each rx port queue pair */
> > +		for (j = 0; j < conf[i].num_rx_queues; j++) {
> > +			struct ethdev_rx_node_main *rx_node_data;
> > +			struct rte_node_register *rx_node;
> > +			ethdev_rx_node_elem_t *elem;
> > +
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			rx_node = ethdev_rx_node_get();
> > +			snprintf(name, sizeof(name), "%u-%u", port_id, j);
> > +			/* Clone a new rx node with same edges as parent */
> > +			id = rte_node_clone(rx_node->id, name);
> > +			if (id == RTE_NODE_ID_INVALID)
> > +				return -EIO;
> > +
> > +			/* Add it to list of ethdev rx nodes for lookup */
> > +			elem = malloc(sizeof(ethdev_rx_node_elem_t));
> > +			if (elem == NULL)
> > +				return -ENOMEM;
> > +			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> > +			elem->ctx.port_id = port_id;
> > +			elem->ctx.queue_id = j;
> > +			elem->nid = id;
> > +			elem->next = rx_node_data->head;
> > +			rx_node_data->head = elem;
> > +
> > +			printf("ethdev:: Rx node %s-%s: is at %u\n",
> rx_node-
> > >name, name, id);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct ethdev_rx_node_main *rx_node_data;
> > +	ethdev_rx_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	uint16_t queue, queue_id, port_id;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint32_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +		port_id = qconf->rx_queue_list[queue].port_id;
> > +		queue_id = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id,
> > queue_id);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Prepare the actual name of the cloned node */
> > +		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"punt_kernel-*"};
> > +	uint16_t queueid, portid, queue;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	rte_node_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +
> > +		portid = qconf->rx_queue_list[queue].port_id;
> > +		queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid,
> > queueid);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		next_node = qconf->punt_kernel_node_name;
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], next_node);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct kernel_recv_node_main *rx_node_data;
> > +	kernel_recv_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = kernel_recv_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.recv_info->cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint16_t port_id;
> > +	rte_node_t id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	id = rte_node_from_name(qconf->kernel_recv_node_name);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], qconf-
> > >kernel_recv_node_name);
> > +
> > +	port_id = lcore_id;
> > +
> > +	if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +		/* Use available port_id */
> > +		RTE_ETH_FOREACH_DEV(port_id) {
> > +			if ((enabled_port_mask & (1 << port_id)) != 0)
> > +				break;
> > +		}
> > +	}
> > +
> > +	if (!rte_eth_dev_is_valid_port(port_id)) {
> > +		printf("Port %u is not present on the board\n", port_id);
> > +		return -1;
> > +	}
> > +
> > +	/* Prepare the actual name of the cloned node */
> > +	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +	/* Update ethdev_rx node edges */
> > +	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node,
> 1);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], name);
> > +
> > +	/* Update node_next details */
> > +	update_kernel_recv_node_next(id, pattern[0]);
> > +
> > +	return 0;
> > +}
> 
> These explicitly update node and link could be put in a independent stage,
> like graph_config.
> 
> Also, I wonder if update link could be more generic. Cause they have some
> similar steps.
> 
> BTY, In this patch, it could support rx->tx first.
> Then add a new patch to support other topo(kernel_recv, punt_kernel and
> l3fwd).
> And we could enable some help function in another patch.
> It could make the patch small.

Yes, make sense, will rework on this.
> 
> > +
> > +uint32_t
> > +ethdev_ports_setup(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint32_t nb_tx_queue, nb_lcores;
> > +	uint32_t nb_ports, nb_conf = 0;
> > +	uint8_t nb_rx_queue;
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	nb_ports = rte_eth_dev_count_avail();
> > +	nb_lcores = rte_lcore_count();
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		/* Init port */
> > +		printf("Initializing port %d ... ", portid);
> > +		fflush(stdout);
> > +
> > +		nb_rx_queue = get_port_n_rx_queues(portid);
> > +		nb_tx_queue = nb_lcores;
> > +		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> > +			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> > +		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n",
> > nb_rx_queue, nb_tx_queue);
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		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 !=
> > +		    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, 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_rx_queue,
> nb_tx_queue,
> > &local_port_conf);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Cannot configure device:
> > err=%d, port=%d\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=%d\n",
> > +				 ret, portid);
> > +
> > +		/* Init memory */
> > +		if (!per_port_pool)
> > +			ret = init_mem(0, NB_MBUF(nb_ports));
> > +		else
> > +			ret = init_mem(portid, NB_MBUF(1));
> > +
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> > +
> > +		/* Setup ethdev node config */
> > +		ethdev_conf[nb_conf].port_id = portid;
> > +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> > +		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> > +		if (!per_port_pool)
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> > +
> > +		else
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> > +		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> > +
> > +		nb_conf++;
> > +	}
> > +
> > +	return nb_conf;
> > +}
> > +
> > +void
> > +ethdev_rxq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint16_t queueid, portid;
> > +	struct lcore_conf *qconf;
> > +	uint8_t queue, socketid;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +		qconf = &lcore_conf[lcore_id];
> > +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> > +		fflush(stdout);
> > +
> > +		/* Init RX queues */
> > +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +			struct rte_eth_rxconf rxq_conf;
> > +
> > +			portid = qconf->rx_queue_list[queue].port_id;
> > +			queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> > +			fflush(stdout);
> > +
> > +			rte_eth_dev_info_get(portid, &dev_info);
> > +			rxq_conf = dev_info.default_rxconf;
> > +			rxq_conf.offloads = port_conf.rxmode.offloads;
> > +			if (!per_port_pool)
> > +				ret = rte_eth_rx_queue_setup(portid,
> queueid,
> > nb_rxd, socketid,
> > +							     &rxq_conf,
> > pktmbuf_pool[0][socketid]);
> > +			else
> > +				ret = rte_eth_rx_queue_setup(portid,
> queueid,
> > nb_rxd, socketid,
> > +							     &rxq_conf,
> > +
> > pktmbuf_pool[portid][socketid]);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +
> > +			snprintf(qconf->rx_queue_list[queue].node_name,
> > RTE_NODE_NAMESIZE,
> > +				 "ethdev_rx-%u-%u", portid, queueid);
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +void
> > +ethdev_txq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	struct rte_eth_txconf *txconf;
> > +	uint16_t queueid, portid;
> > +	uint32_t lcore_id;
> > +	uint8_t socketid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		/* Init one TX queue per (lcore,port) pair */
> > +		queueid = 0;
> > +		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +			if (rte_lcore_is_enabled(lcore_id) == 0)
> > +				continue;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("txq=%u,%d,%d ", lcore_id, queueid,
> socketid);
> > +			fflush(stdout);
> > +
> > +			txconf = &dev_info.default_txconf;
> > +			txconf->offloads = local_port_conf.txmode.offloads;
> > +			ret = rte_eth_tx_queue_setup(portid, queueid,
> nb_txd,
> > socketid, txconf);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +			queueid++;
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +int
> > +configure_graph_nodes(uint64_t valid_nodes)
> > +{
> > +	int ret = 0;
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> > +		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> > +		if (ret) {
> > +			printf("ethdev_rx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> > +		ret = ethdev_tx_node_configure();
> > +		if (ret) {
> > +			printf("ethdev_tx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> > +		ret = punt_kernel_node_configure();
> > +		if (ret) {
> > +			printf("punt_kernel_node_configure: err=%d\n",
> ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
> > +		ret = kernel_recv_node_configure();
> > +		if (ret) {
> > +			printf("kernel_recv_node_configure: err=%d\n",
> ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static int
> > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> > +{
> > +	int ret = 0;
> > +
> > +	num_patterns = 0;
> > +
> > +	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_ETHDEV_RX_NODE)) {
> > +		ret = link_ethdev_rx_to_tx_node(lcore_id);
> > +		if (ret) {
> > +			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE |
> > TEST_GRAPH_PUNT_KERNEL_NODE)) {
> > +		link_ethdev_rx_to_punt_kernel_node(lcore_id);
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_KERNEL_RECV_NODE)) {
> > +		link_kernel_recv_to_ethdev_tx_node(lcore_id);
> > +	} else {
> > +		printf("Invalid node map\n");
> > +		ret = -EINVAL;
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +void
> > +start_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +
> > +		ret = rte_eth_dev_start(portid);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d,
> > port=%d\n", ret, portid);
> > +
> > +		if (promiscuous_on)
> > +			rte_eth_promiscuous_enable(portid);
> > +	}
> > +}
> > +
> > +void
> > +stop_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +		printf("Closing port %d...", portid);
> > +		ret = rte_eth_dev_stop(portid);
> > +		if (ret != 0)
> > +			printf("Failed to stop port %u: %s\n", portid,
> > rte_strerror(-ret));
> > +		rte_eth_dev_close(portid);
> close should be in exit stage or when application is killed.

ack
> 
> > +	}
> > +}
> > +
> > +int
> > +create_graph(uint64_t valid_nodes)
> > +{
> > +	struct rte_graph_param graph_conf;
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	node_patterns = malloc(MAX_NODE_PATTERNS *
> > sizeof(*node_patterns));
> > +	if (!node_patterns)
> > +		return -ENOMEM;
> > +
> > +	memset(&graph_conf, 0, sizeof(graph_conf));
> > +	graph_conf.node_patterns = node_patterns;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		rte_graph_t graph_id;
> > +		rte_edge_t i;
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Skip graph creation if no source exists */
> > +		if (!qconf->n_rx_queue)
> > +			continue;
> > +
> > +		ret = link_graph_nodes(valid_nodes, lcore_id);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "link_graph_nodes():
> failed\n");
> > +
> > +		for (i = 0; i < num_patterns; i++) {
> > +			graph_conf.node_patterns[i] = node_pattern[i];
> > +			printf("%s,", graph_conf.node_patterns[i]);
> > +		}
> > +		printf("\n");
> > +
> > +		graph_conf.nb_node_patterns = num_patterns;
> > +
> > +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> > +
> > +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> > lcore_id);
> > +
> > +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> > +		if (graph_id == RTE_GRAPH_ID_INVALID)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_create():
> graph_id
> > invalid for lcore%u\n",
> > +				 lcore_id);
> > +
> > +		qconf->graph_id = graph_id;
> > +		qconf->graph = rte_graph_lookup(qconf->name);
> > +
> > +		if (!qconf->graph)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph
> %s
> > not found\n",
> > +				 qconf->name);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +destroy_graph(void)
> > +{
> > +	uint32_t lcore_id;
> > +	rte_graph_t id;
> > +	int ret;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +	{
> > +		if (lcore_conf[lcore_id].graph) {
> > +			id =
> rte_graph_from_name(lcore_conf[lcore_id].name);
> > +			if (rte_graph_destroy(id)) {
> > +				printf("graph_id %u destroy failed.\n", id);
> > +				ret = -1;
> > +			}
> > +		}
> > +	}
> > +
> > +	if (node_patterns)
> > +		free(node_patterns);
> 
> Cause this cmd could be called twice in interactive-mode, we should set it to
> NULL after free.

ack
> 
> > +
> > +	return ret;
> > +}
> > +
> > +int
> > +main(int argc, char **argv)
> > +{
> > +	uint64_t valid_nodes;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	graph_walk_quit = false;
> > +	force_quit = false;
> > +	interactive = 0;
> > +
> > +	node_patterns = NULL;
> > +
> > +	signal(SIGINT, signal_handler);
> > +	signal(SIGTERM, signal_handler);
> > +
> > +	testgraph_logtype = rte_log_register("testgraph");
> > +	if (testgraph_logtype < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot register log type");
> > +
> > +	set_default_node_pattern();
> > +
> > +	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> > +
> > +	ret = rte_eal_init(argc, argv);
> > +	if (ret < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n",
> > rte_strerror(rte_errno));
> > +	argc -= ret;
> > +	argv += ret;
> > +
> > +	if (argc > 1) {
> > +		ret = parse_cmdline_args(argc, argv);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Invalid command line
> > parameters\n");
> > +	}
> > +
> > +#ifdef RTE_LIB_CMDLINE
> > +	if (init_cmdline() != 0)
> > +		rte_exit(EXIT_FAILURE, "Could not initialise cmdline
> > context.\n");
> > +
> > +	if (interactive == 1) {
> > +		prompt();
> > +	} else
> > +#endif
> > +	{
> > +		if (validate_config() < 0)
> > +			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> > +
> > +		ret = validate_node_names(&valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "validate_node_names:
> > err=%d\n", ret);
> > +
> > +		nb_conf = ethdev_ports_setup();
> > +
> > +		ethdev_rxq_configure();
> > +
> > +		ethdev_txq_configure();
> > +
> > +		ret = configure_graph_nodes(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> > err=%d\n", ret);
> > +
> > +		ret = create_graph(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n",
> ret);
> > +
> > +		stats = create_graph_cluster_stats();
> > +		if (stats == NULL)
> > +			rte_exit(EXIT_FAILURE,
> "create_graph_cluster_stats()
> > failed\n");
> > +
> > +		check_all_ports_link_status(enabled_port_mask);
> > +
> > +		start_eth_ports();
> > +
> > +		/* Launch per-lcore init on every worker lcore */
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> > +
> > +		/* Accumulate and print stats on main until exit */
> > +		if (rte_graph_has_stats_feature())
> > +			print_stats();
> > +
> > +		/* Wait for worker cores to exit */
> > +		RTE_LCORE_FOREACH_WORKER(lcore_id) {
> > +			ret = rte_eal_wait_lcore(lcore_id);
> > +			if (ret < 0)
> > +				break;
> > +		}
> > +
> > +		ret = destroy_graph();
> > +
> > +		stop_eth_ports();
> > +	}
> > +
> > +	/* clean up the EAL */
> > +	ret = rte_eal_cleanup();
> > +	if (ret != 0)
> > +		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-
> > ret));
> > +
> > +	return EXIT_SUCCESS;
> > +}
> > diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> > new file mode 100644
> > index 0000000000..ea1f8ef4fa
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.h
> > @@ -0,0 +1,91 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _TESTGRAPH_H_
> > +#define _TESTGRAPH_H_
> > +
> > +#include <stdbool.h>
> > +
> > +#include <rte_ethdev.h>
> > +#include <rte_graph.h>
> > +#include <rte_node_eth_api.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +
> > +
> > +#define MAX_LCORE_PARAMS 1024
> > +#define MAX_NODE_PATTERNS 128
> > +
> > +#ifndef BIT_ULL
> > +#define BIT_ULL(nr) (1ULL << (nr))
> > +#endif
> > +
> > +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> > +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> > +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> > +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> > +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> > +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> > +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> > +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> > +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> > +
> > +static volatile bool force_quit;
> > +
> > +extern struct rte_node_ethdev_config
> ethdev_conf[RTE_MAX_ETHPORTS];
> > +extern uint32_t enabled_port_mask;
> > +extern uint32_t nb_conf;
> > +
> > +extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by
> > default. */
> > +extern uint8_t interactive;	   /**< interactive mode is disabled by
> default.
> > */
> > +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +extern int numa_on;		   /**< NUMA is enabled by default.
> */
> > +extern int per_port_pool;
> > +
> > +extern volatile bool graph_walk_quit;
> > +extern volatile bool run_graph_walk;
> > +extern struct rte_graph_cluster_stats *stats;
> > +
> > +extern char
> node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> > +extern uint8_t num_patterns;
> > +
> > +struct node_list {
> > +	const char *nodes[MAX_NODE_PATTERNS];
> > +	uint64_t test_id;
> > +	uint8_t size;
> > +};
> > +
> > +struct lcore_params {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	uint8_t lcore_id;
> > +} __rte_cache_aligned;
> > +
> > +void prompt(void);
> > +void prompt_exit(void);
> > +int init_cmdline(void);
> > +int validate_config(void);
> > +int parse_cmdline_args(int argc, char **argv);
> > +uint32_t ethdev_ports_setup(void);
> > +void ethdev_rxq_configure(void);
> > +void ethdev_txq_configure(void);
> > +void start_eth_ports(void);
> > +void stop_eth_ports(void);
> > +int create_graph(uint64_t valid_nodes);
> > +int destroy_graph(void);
> > +int graph_main_loop(void *conf);
> > +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> > +void check_all_ports_link_status(uint32_t port_mask);
> > +int configure_graph_nodes(uint64_t valid_nodes);
> > +
> > +int parse_config(const char *q_arg);
> > +int parse_node_patterns(const char *q_arg);
> > +int validate_node_names(uint64_t *valid_nodes);
> > +void cleanup_node_pattern(void);
> > +
> > +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> > +	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt,
> ##args)
> > +
> > +#endif /* _TESTGRAPH_H_ */
> > diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> > index 6f84fc31ff..f18c508fa2 100644
> > --- a/doc/guides/tools/index.rst
> > +++ b/doc/guides/tools/index.rst
> > @@ -20,6 +20,7 @@ DPDK Tools User Guides
> >      cryptoperf
> >      comp_perf
> >      testeventdev
> > +    testgraph
> >      testregex
> >      testmldev
> >      dts
> > diff --git a/doc/guides/tools/testgraph.rst
> b/doc/guides/tools/testgraph.rst
> > new file mode 100644
> > index 0000000000..3c1e058724
> > --- /dev/null
> > +++ b/doc/guides/tools/testgraph.rst
> > @@ -0,0 +1,131 @@
> > +..  SPDX-License-Identifier: BSD-3-Clause
> > +    Copyright(C) 2023 Marvell International Ltd.
> > +
> > +dpdk-test-graph Application
> > +===========================
> > +
> > +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK)
> application
> > that allows
> > +exercising various graph library features. This application has a generic
> > framework to add
> > +new test configurations and expand test coverage to verify the
> functionality of
> > graph nodes
> > +and observe the graph cluster statistics.
> > +
> > +Running the Application
> > +-----------------------
> > +
> > +The application has a number of command line options:
> > +
> > +.. code-block:: console
> > +
> > +   dpdk-test-eventdev [EAL Options] -- [application options]
> > +
> > +EAL Options
> > +~~~~~~~~~~~
> > +
> > +The following are the EAL command-line options that can be used in
> > conjunction
> > +with the ``dpdk-test-graph`` application.
> > +See the DPDK Getting Started Guides for more information on these
> options.
> > +
> > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > +
> > +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> > +        list of cores to use.
> > +
> > +Application Options
> > +~~~~~~~~~~~~~~~~~~~
> > +
> > +The following are the application command-line options:
> > +
> > +* ``-p <n>``
> > +
> > +        Set the ethdev port mask.
> > +
> > +* ``-P``
> > +
> > +        Set the ethdev ports in promiscuous mode.
> > +
> > +* ``--config <config>``
> > +
> > +        Set the Rxq configuration.
> > +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> > +
> > +* ``--node-pattern <n>``
> > +
> > +        Set the node patterns to use in graph creation.
> > +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> > +
> > +* ``--per-port-pool``
> > +
> > +        Use separate buffer pool per port.
> > +
> > +* ``--no-numa``
> > +
> > +        Disable numa awareness.
> > +
> > +* ``--interactive``
> > +
> > +        Switch to interactive mode.
> > +
> > +Running the Tool
> > +~~~~~~~~~~~~~~~~
> > +
> > +Here is the sample command line to run simple iofwd test::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> > +
> > +Below is a sample command line to punt rx packets to kernel::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> > +
> > +Interactive mode
> > +~~~~~~~~~~~~~~~~
> > +
> > +Tool uses ``--interactive`` command line option to enter interactive mode
> and
> > use cmdline options
> > +to setup the required node configurations, create graph and than start
> > graph_walk.
> > +
> > +
> > +testgraph> help
> > +
> > +Help is available for the following sections:
> > +
> > +    help control                    : Start and stop graph walk.
> > +    help display                    : Displaying port, stats and config information.
> > +    help config                     : Configuration information.
> > +    help all                        : All of the above sections.
> > +
> > +testgraph> help all
> > +
> > +Control forwarding:
> > +
> > +start graph_walk
> > + Start graph_walk on worker threads.
> > +
> > +stop graph_walk
> > + Stop worker threads from running graph_walk.
> > +
> > +quit
> > + Quit to prompt.
> > +
> > +
> > +Display:
> > +
> > +show node_list
> > + Display the list of supported nodes.
> > +
> > +show graph_stats
> > + Display the node statistics of graph cluster.
> > +
> > +
> > +Configuration:
> > +
> > +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> > + Set lcore configuration.
> > +
> > +create_graph (node0_name,node1_name,...,nodeX_name)
> > + Create graph instances using the provided node details.
> > +
> > +destroy_graph
> > + Destroy the graph instances.
> > +
> > +testgraph>
> > --
> > 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH 1/4] node: add pkt punt to kernel node
  2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
@ 2023-05-29 17:29   ` Jerin Jacob
  2023-05-31 12:36     ` [EXT] " Vamsi Krishna Attunuru
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-05-29 17:29 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, jerinj, ndabilpuram

On Fri, Apr 21, 2023 at 11:33 AM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>

Subject suggestion:  node/punt: support punting packet to kernel

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH 2/4] node: add a node to receive pkts from kernel
  2023-04-21  6:02 ` [PATCH 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
@ 2023-05-29 17:39   ` Jerin Jacob
  2023-06-01  2:40     ` [EXT] " Vamsi Krishna Attunuru
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-05-29 17:39 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, jerinj, ndabilpuram

On Fri, Apr 21, 2023 at 11:33 AM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> Patch adds a node to receive packets from kernel
> over a raw socket.

Subjection suggestion: node/kernel_rx: support receving packet from kernel
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  doc/guides/prog_guide/graph_lib.rst |   7 +
>  lib/node/kernel_recv.c              | 277 ++++++++++++++++++++++++++++
>  lib/node/kernel_recv_priv.h         |  74 ++++++++
>  lib/node/meson.build                |   1 +
>  4 files changed, 359 insertions(+)
>
> diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
> index b3b5b14827..1057f16de8 100644
> --- a/doc/guides/prog_guide/graph_lib.rst
> +++ b/doc/guides/prog_guide/graph_lib.rst
> @@ -402,3 +402,10 @@ on the raw socket.
>
>  Aftering sending the burst of packets to kernel, this node redirects the same
>  objects to pkt_drop node to free up the packet buffers.
> +
> +kernel_recv

Better to change node name to kernel_rx

> +~~~~~~~~~~~
> +This node receives packets from kernel over a raw socket interface. Uses ``poll``
> +function to poll on the socket fd for ``POLLIN`` events to read the packets from
> +raw socket to stream buffer and does ``rte_node_next_stream_move()`` when there
> +are received packets.

You can tell typical use case for punt and kernel_tx node. i.e
expection path handling.
Also, may consider chnage to kernel_tx  for punt node.


> diff --git a/lib/node/kernel_recv.c b/lib/node/kernel_recv.c
> new file mode 100644
> index 0000000000..361dcc3b5f
> --- /dev/null
> +++ b/lib/node/kernel_recv.c
> @@ -0,0 +1,277 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fcntl.h>
> +#include <poll.h>
> +#include <stdlib.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include <rte_debug.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_ip.h>
> +#include <rte_malloc.h>
> +#include <rte_mbuf.h>
> +#include <rte_mempool.h>
> +#include <rte_net.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "kernel_recv_priv.h"
> +#include "node_private.h"
> +
> +static struct kernel_recv_node_main kernel_recv_main;

Try to remove global varible.

> +#ifndef __INCLUDE_KERNEL_RECV_PRIV_H__

No need to add INCLUDE

> +#define __INCLUDE_KERNEL_RECV_PRIV_H__
> +
> +#define KERN_RECV_CACHE_COUNT 64
> +
> +typedef struct kernel_recv_info {
> +       struct rte_mbuf *rx_bufs[KERN_RECV_CACHE_COUNT];
> +       uint16_t cls_next;
> +       uint16_t idx;
> +       uint16_t cnt;
> +       int sock;
> +} kernel_recv_info_t;
> +
> +/**
> + * @internal
> + *
> + * Kernel Recv node context structure.

Across the patch series, no need for Doxgen comments for internal functions.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v2 4/4] app: add testgraph application
  2023-05-22  7:07       ` Vamsi Krishna Attunuru
@ 2023-05-30  7:34         ` Jerin Jacob
  2023-06-01  2:44           ` [EXT] " Vamsi Krishna Attunuru
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-05-30  7:34 UTC (permalink / raw)
  To: Vamsi Krishna Attunuru
  Cc: Yan, Zhirun, dev, thomas, Jerin Jacob Kollanukkaran,
	Nithin Kumar Dabilpuram, Liang, Cunming, Wang, Haiyue,
	Sunil Kumar Kori

On Mon, May 22, 2023 at 12:37 PM Vamsi Krishna Attunuru
<vattunuru@marvell.com> wrote:

> > > +static int
> > > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> > > +{
> > > +   int ret = 0;
> > > +
> > > +   num_patterns = 0;
> > > +
> > > +   if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |


I think, if we need to extend the C code for adding new use case, then
it will not scale.
IMO, We should look at more runtime and file based interface.
Something like https://github.com/DPDK/dpdk/blob/main/examples/ip_pipeline/examples/l2fwd.cli
In nutshell,
1) File based interface to kick-start the valid use case enablement
2) Less logic in C code and everything should be driven from config file
3) Allow runtime change. examples/ip_pipeline provides telent
interface to update . Similar concept can be followed.

I think, we should push the app for next release. Not on this release.
Sorry for reviewing late.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v2 1/4] node: add pkt punt to kernel node
  2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
@ 2023-05-30  8:16     ` Nithin Dabilpuram
  2023-05-31 12:35       ` [EXT] " Vamsi Krishna Attunuru
  0 siblings, 1 reply; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-05-30  8:16 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, thomas, jerinj, ndabilpuram

On Tue, Apr 25, 2023 at 6:45 PM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> Patch adds a node to punt the packets to kernel over
> a raw socket.
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  doc/guides/prog_guide/graph_lib.rst |  10 +++
>  lib/node/meson.build                |   1 +
>  lib/node/punt_kernel.c              | 125 ++++++++++++++++++++++++++++
>  lib/node/punt_kernel_priv.h         |  36 ++++++++
>  4 files changed, 172 insertions(+)
>
> diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
> index 1cfdc86433..b3b5b14827 100644
> --- a/doc/guides/prog_guide/graph_lib.rst
> +++ b/doc/guides/prog_guide/graph_lib.rst
> @@ -392,3 +392,13 @@ null
>  ~~~~
>  This node ignores the set of objects passed to it and reports that all are
>  processed.
> +
> +punt_kernel
> +~~~~~~~~~~~
> +This node punts the packets to kernel using a raw socket interface. For sending
> +the received packets, raw socket uses the packet's destination IP address in
> +sockaddr_in address structure and node uses ``sendto`` function to send data
> +on the raw socket.
> +
> +Aftering sending the burst of packets to kernel, this node redirects the same
> +objects to pkt_drop node to free up the packet buffers.
> diff --git a/lib/node/meson.build b/lib/node/meson.build
> index dbdf673c86..48c2da73f7 100644
> --- a/lib/node/meson.build
> +++ b/lib/node/meson.build
> @@ -17,6 +17,7 @@ sources = files(
>          'null.c',
>          'pkt_cls.c',
>          'pkt_drop.c',
> +        'punt_kernel.c',
>  )
>  headers = files('rte_node_ip4_api.h', 'rte_node_eth_api.h')
>  # Strict-aliasing rules are violated by uint8_t[] to context size casts.
> diff --git a/lib/node/punt_kernel.c b/lib/node/punt_kernel.c
> new file mode 100644
> index 0000000000..e5dd15b759
> --- /dev/null
> +++ b/lib/node/punt_kernel.c
> @@ -0,0 +1,125 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fcntl.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include <rte_debug.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_ip.h>
> +
> +#include "node_private.h"
> +#include "punt_kernel_priv.h"
> +
> +static __rte_always_inline void
> +punt_kernel_process_mbuf(struct rte_node *node, struct rte_mbuf **mbufs, uint16_t cnt)
> +{
> +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
> +       struct sockaddr_in sin = {0};
> +       struct rte_ipv4_hdr *ip4;
> +       size_t len;
> +       char *buf;
> +       int i;
> +
> +       for (i = 0; i < cnt; i++) {
> +               ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
> +               len = rte_pktmbuf_data_len(mbufs[i]);
> +               buf = (char *)ip4;
> +
> +               sin.sin_family = AF_INET;
> +               sin.sin_port = 0;
> +               sin.sin_addr.s_addr = ip4->dst_addr;
> +
> +               if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
> +                       node_err("punt_kernel", "Unable to send packets: %s\n", strerror(errno));
> +       }
> +}
> +
> +static uint16_t
> +punt_kernel_node_process(struct rte_graph *graph __rte_unused, struct rte_node *node, void **objs,
> +                        uint16_t nb_objs)
> +{
> +       struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
> +       uint16_t obj_left = nb_objs;
> +
> +#define PREFETCH_CNT 4
> +
> +       while (obj_left >= 12) {
> +               /* Prefetch next-next mbufs */
> +               rte_prefetch0(pkts[8]);
> +               rte_prefetch0(pkts[9]);
> +               rte_prefetch0(pkts[10]);
> +               rte_prefetch0(pkts[11]);
> +
> +               /* Prefetch next mbuf data */
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *, pkts[7]->l2_len));
> +
> +               punt_kernel_process_mbuf(node, pkts, PREFETCH_CNT);
> +
> +               obj_left -= PREFETCH_CNT;
> +               pkts += PREFETCH_CNT;
> +       }
> +
> +       while (obj_left > 0) {
> +               punt_kernel_process_mbuf(node, pkts, 1);
> +
> +               obj_left--;
> +               pkts++;
> +       }
> +
> +       rte_node_next_stream_move(graph, node, PUNT_KERNEL_NEXT_PKT_DROP);

Packet drop node signifies that packet is dropped due to some reason
and not consumed. Since here the packet is really not dropped but
consumed by Kernel, can we avoid using pkt drop node
and instead free pkts directly ?

> +
> +       return nb_objs;
> +}
> +
> +static int
> +punt_kernel_node_init(const struct rte_graph *graph __rte_unused, struct rte_node *node)
> +{
> +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
> +
> +       ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
> +       if (ctx->sock < 0)
> +               node_err("punt_kernel", "Unable to open RAW socket\n");
> +
> +       return 0;
> +}
> +
> +static void
> +punt_kernel_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
> +{
> +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node->ctx;
> +
> +       if (ctx->sock >= 0) {
> +               close(ctx->sock);
> +               ctx->sock = -1;
> +       }
> +}
> +
> +static struct rte_node_register punt_kernel_node_base = {
> +       .process = punt_kernel_node_process,
> +       .name = "punt_kernel",
> +
> +       .init = punt_kernel_node_init,
> +       .fini = punt_kernel_node_fini,
> +
> +       .nb_edges = PUNT_KERNEL_NEXT_MAX,
> +       .next_nodes = {
> +                       [PUNT_KERNEL_NEXT_PKT_DROP] = "pkt_drop",
> +       },
> +};
> +
> +struct rte_node_register *
> +punt_kernel_node_get(void)
> +{
> +       return &punt_kernel_node_base;
> +}
> +
> +RTE_NODE_REGISTER(punt_kernel_node_base);
> diff --git a/lib/node/punt_kernel_priv.h b/lib/node/punt_kernel_priv.h
> new file mode 100644
> index 0000000000..25ba2072e7
> --- /dev/null
> +++ b/lib/node/punt_kernel_priv.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef __INCLUDE_PUNT_KERNEL_PRIV_H__
> +#define __INCLUDE_PUNT_KERNEL_PRIV_H__
> +
> +struct punt_kernel_node_elem;
> +struct punt_kernel_node_ctx;
> +typedef struct punt_kernel_node_elem punt_kernel_node_elem_t;
> +
> +/**
> + * @internal
> + *
> + * PUNT Kernel node context structure.
> + */
> +typedef struct punt_kernel_node_ctx {
> +       int sock;
> +} punt_kernel_node_ctx_t;
> +
> +enum punt_kernel_next_nodes {
> +       PUNT_KERNEL_NEXT_PKT_DROP,
> +       PUNT_KERNEL_NEXT_MAX,
> +};
> +
> +/**
> + * @internal
> + *
> + * Get the PUNT Kernel node.
> + *
> + * @return
> + *   Pointer to the PUNT Kernel node.
> + */
> +struct rte_node_register *punt_kernel_node_get(void);
> +
> +#endif /* __INCLUDE_PUNT_KERNEL_PRIV_H__ */
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 1/4] node: add pkt punt to kernel node
  2023-05-30  8:16     ` Nithin Dabilpuram
@ 2023-05-31 12:35       ` Vamsi Krishna Attunuru
  0 siblings, 0 replies; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-05-31 12:35 UTC (permalink / raw)
  To: Nithin Dabilpuram
  Cc: dev, thomas, Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram



> -----Original Message-----
> From: Nithin Dabilpuram <nithind1988@gmail.com>
> Sent: Tuesday, May 30, 2023 1:47 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Cc: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>
> Subject: [EXT] Re: [PATCH v2 1/4] node: add pkt punt to kernel node
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Tue, Apr 25, 2023 at 6:45 PM Vamsi Attunuru <vattunuru@marvell.com>
> wrote:
> >
> > Patch adds a node to punt the packets to kernel over a raw socket.
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
> >  doc/guides/prog_guide/graph_lib.rst |  10 +++
> >  lib/node/meson.build                |   1 +
> >  lib/node/punt_kernel.c              | 125 ++++++++++++++++++++++++++++
> >  lib/node/punt_kernel_priv.h         |  36 ++++++++
> >  4 files changed, 172 insertions(+)
> >
> > diff --git a/doc/guides/prog_guide/graph_lib.rst
> > b/doc/guides/prog_guide/graph_lib.rst
> > index 1cfdc86433..b3b5b14827 100644
> > --- a/doc/guides/prog_guide/graph_lib.rst
> > +++ b/doc/guides/prog_guide/graph_lib.rst
> > @@ -392,3 +392,13 @@ null
> >  ~~~~
> >  This node ignores the set of objects passed to it and reports that
> > all are  processed.
> > +
> > +punt_kernel
> > +~~~~~~~~~~~
> > +This node punts the packets to kernel using a raw socket interface.
> > +For sending the received packets, raw socket uses the packet's
> > +destination IP address in sockaddr_in address structure and node uses
> > +``sendto`` function to send data on the raw socket.
> > +
> > +Aftering sending the burst of packets to kernel, this node redirects
> > +the same objects to pkt_drop node to free up the packet buffers.
> > diff --git a/lib/node/meson.build b/lib/node/meson.build index
> > dbdf673c86..48c2da73f7 100644
> > --- a/lib/node/meson.build
> > +++ b/lib/node/meson.build
> > @@ -17,6 +17,7 @@ sources = files(
> >          'null.c',
> >          'pkt_cls.c',
> >          'pkt_drop.c',
> > +        'punt_kernel.c',
> >  )
> >  headers = files('rte_node_ip4_api.h', 'rte_node_eth_api.h')  #
> > Strict-aliasing rules are violated by uint8_t[] to context size casts.
> > diff --git a/lib/node/punt_kernel.c b/lib/node/punt_kernel.c new file
> > mode 100644 index 0000000000..e5dd15b759
> > --- /dev/null
> > +++ b/lib/node/punt_kernel.c
> > @@ -0,0 +1,125 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <fcntl.h>
> > +#include <sys/ioctl.h>
> > +#include <sys/socket.h>
> > +#include <unistd.h>
> > +
> > +#include <rte_debug.h>
> > +#include <rte_ethdev.h>
> > +#include <rte_graph.h>
> > +#include <rte_graph_worker.h>
> > +#include <rte_ip.h>
> > +
> > +#include "node_private.h"
> > +#include "punt_kernel_priv.h"
> > +
> > +static __rte_always_inline void
> > +punt_kernel_process_mbuf(struct rte_node *node, struct rte_mbuf
> > +**mbufs, uint16_t cnt) {
> > +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t *)node-
> >ctx;
> > +       struct sockaddr_in sin = {0};
> > +       struct rte_ipv4_hdr *ip4;
> > +       size_t len;
> > +       char *buf;
> > +       int i;
> > +
> > +       for (i = 0; i < cnt; i++) {
> > +               ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
> > +               len = rte_pktmbuf_data_len(mbufs[i]);
> > +               buf = (char *)ip4;
> > +
> > +               sin.sin_family = AF_INET;
> > +               sin.sin_port = 0;
> > +               sin.sin_addr.s_addr = ip4->dst_addr;
> > +
> > +               if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin,
> sizeof(sin)) < 0)
> > +                       node_err("punt_kernel", "Unable to send packets: %s\n",
> strerror(errno));
> > +       }
> > +}
> > +
> > +static uint16_t
> > +punt_kernel_node_process(struct rte_graph *graph __rte_unused,
> struct rte_node *node, void **objs,
> > +                        uint16_t nb_objs) {
> > +       struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
> > +       uint16_t obj_left = nb_objs;
> > +
> > +#define PREFETCH_CNT 4
> > +
> > +       while (obj_left >= 12) {
> > +               /* Prefetch next-next mbufs */
> > +               rte_prefetch0(pkts[8]);
> > +               rte_prefetch0(pkts[9]);
> > +               rte_prefetch0(pkts[10]);
> > +               rte_prefetch0(pkts[11]);
> > +
> > +               /* Prefetch next mbuf data */
> > +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]-
> >l2_len));
> > +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]-
> >l2_len));
> > +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]-
> >l2_len));
> > +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *,
> > + pkts[7]->l2_len));
> > +
> > +               punt_kernel_process_mbuf(node, pkts, PREFETCH_CNT);
> > +
> > +               obj_left -= PREFETCH_CNT;
> > +               pkts += PREFETCH_CNT;
> > +       }
> > +
> > +       while (obj_left > 0) {
> > +               punt_kernel_process_mbuf(node, pkts, 1);
> > +
> > +               obj_left--;
> > +               pkts++;
> > +       }
> > +
> > +       rte_node_next_stream_move(graph, node,
> > + PUNT_KERNEL_NEXT_PKT_DROP);
> 
> Packet drop node signifies that packet is dropped due to some reason and
> not consumed. Since here the packet is really not dropped but consumed by
> Kernel, can we avoid using pkt drop node and instead free pkts directly ?
> 
Ack, will fix in next version.
> > +
> > +       return nb_objs;
> > +}
> > +
> > +static int
> > +punt_kernel_node_init(const struct rte_graph *graph __rte_unused,
> > +struct rte_node *node) {
> > +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t
> > +*)node->ctx;
> > +
> > +       ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
> > +       if (ctx->sock < 0)
> > +               node_err("punt_kernel", "Unable to open RAW
> > + socket\n");
> > +
> > +       return 0;
> > +}
> > +
> > +static void
> > +punt_kernel_node_fini(const struct rte_graph *graph __rte_unused,
> > +struct rte_node *node) {
> > +       punt_kernel_node_ctx_t *ctx = (punt_kernel_node_ctx_t
> > +*)node->ctx;
> > +
> > +       if (ctx->sock >= 0) {
> > +               close(ctx->sock);
> > +               ctx->sock = -1;
> > +       }
> > +}
> > +
> > +static struct rte_node_register punt_kernel_node_base = {
> > +       .process = punt_kernel_node_process,
> > +       .name = "punt_kernel",
> > +
> > +       .init = punt_kernel_node_init,
> > +       .fini = punt_kernel_node_fini,
> > +
> > +       .nb_edges = PUNT_KERNEL_NEXT_MAX,
> > +       .next_nodes = {
> > +                       [PUNT_KERNEL_NEXT_PKT_DROP] = "pkt_drop",
> > +       },
> > +};
> > +
> > +struct rte_node_register *
> > +punt_kernel_node_get(void)
> > +{
> > +       return &punt_kernel_node_base; }
> > +
> > +RTE_NODE_REGISTER(punt_kernel_node_base);
> > diff --git a/lib/node/punt_kernel_priv.h b/lib/node/punt_kernel_priv.h
> > new file mode 100644 index 0000000000..25ba2072e7
> > --- /dev/null
> > +++ b/lib/node/punt_kernel_priv.h
> > @@ -0,0 +1,36 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef __INCLUDE_PUNT_KERNEL_PRIV_H__ #define
> > +__INCLUDE_PUNT_KERNEL_PRIV_H__
> > +
> > +struct punt_kernel_node_elem;
> > +struct punt_kernel_node_ctx;
> > +typedef struct punt_kernel_node_elem punt_kernel_node_elem_t;
> > +
> > +/**
> > + * @internal
> > + *
> > + * PUNT Kernel node context structure.
> > + */
> > +typedef struct punt_kernel_node_ctx {
> > +       int sock;
> > +} punt_kernel_node_ctx_t;
> > +
> > +enum punt_kernel_next_nodes {
> > +       PUNT_KERNEL_NEXT_PKT_DROP,
> > +       PUNT_KERNEL_NEXT_MAX,
> > +};
> > +
> > +/**
> > + * @internal
> > + *
> > + * Get the PUNT Kernel node.
> > + *
> > + * @return
> > + *   Pointer to the PUNT Kernel node.
> > + */
> > +struct rte_node_register *punt_kernel_node_get(void);
> > +
> > +#endif /* __INCLUDE_PUNT_KERNEL_PRIV_H__ */
> > --
> > 2.25.1
> >

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH 1/4] node: add pkt punt to kernel node
  2023-05-29 17:29   ` Jerin Jacob
@ 2023-05-31 12:36     ` Vamsi Krishna Attunuru
  0 siblings, 0 replies; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-05-31 12:36 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: dev, Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram



> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, May 29, 2023 11:00 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Cc: dev@dpdk.org; Jerin Jacob Kollanukkaran <jerinj@marvell.com>; Nithin
> Kumar Dabilpuram <ndabilpuram@marvell.com>
> Subject: [EXT] Re: [PATCH 1/4] node: add pkt punt to kernel node
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Apr 21, 2023 at 11:33 AM Vamsi Attunuru <vattunuru@marvell.com>
> wrote:
> >
> 
> Subject suggestion:  node/punt: support punting packet to kernel

Ack.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH 2/4] node: add a node to receive pkts from kernel
  2023-05-29 17:39   ` Jerin Jacob
@ 2023-06-01  2:40     ` Vamsi Krishna Attunuru
  0 siblings, 0 replies; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-06-01  2:40 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: dev, Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram



> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, May 29, 2023 11:10 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Cc: dev@dpdk.org; Jerin Jacob Kollanukkaran <jerinj@marvell.com>; Nithin
> Kumar Dabilpuram <ndabilpuram@marvell.com>
> Subject: [EXT] Re: [PATCH 2/4] node: add a node to receive pkts from kernel
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Apr 21, 2023 at 11:33 AM Vamsi Attunuru <vattunuru@marvell.com>
> wrote:
> >
> > Patch adds a node to receive packets from kernel over a raw socket.
> 
> Subjection suggestion: node/kernel_rx: support receving packet from kernel
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
> >  doc/guides/prog_guide/graph_lib.rst |   7 +
> >  lib/node/kernel_recv.c              | 277 ++++++++++++++++++++++++++++
> >  lib/node/kernel_recv_priv.h         |  74 ++++++++
> >  lib/node/meson.build                |   1 +
> >  4 files changed, 359 insertions(+)
> >
> > diff --git a/doc/guides/prog_guide/graph_lib.rst
> > b/doc/guides/prog_guide/graph_lib.rst
> > index b3b5b14827..1057f16de8 100644
> > --- a/doc/guides/prog_guide/graph_lib.rst
> > +++ b/doc/guides/prog_guide/graph_lib.rst
> > @@ -402,3 +402,10 @@ on the raw socket.
> >
> >  Aftering sending the burst of packets to kernel, this node redirects
> > the same  objects to pkt_drop node to free up the packet buffers.
> > +
> > +kernel_recv
> 
> Better to change node name to kernel_rx
> 
> > +~~~~~~~~~~~
> > +This node receives packets from kernel over a raw socket interface.
> > +Uses ``poll`` function to poll on the socket fd for ``POLLIN`` events
> > +to read the packets from raw socket to stream buffer and does
> > +``rte_node_next_stream_move()`` when there are received packets.
> 
> You can tell typical use case for punt and kernel_tx node. i.e expection path
> handling.
> Also, may consider chnage to kernel_tx  for punt node.
> 
ack
> 
> > diff --git a/lib/node/kernel_recv.c b/lib/node/kernel_recv.c new file
> > mode 100644 index 0000000000..361dcc3b5f
> > --- /dev/null
> > +++ b/lib/node/kernel_recv.c
> > @@ -0,0 +1,277 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <fcntl.h>
> > +#include <poll.h>
> > +#include <stdlib.h>
> > +#include <sys/ioctl.h>
> > +#include <sys/socket.h>
> > +#include <unistd.h>
> > +
> > +#include <rte_debug.h>
> > +#include <rte_ethdev.h>
> > +#include <rte_graph.h>
> > +#include <rte_graph_worker.h>
> > +#include <rte_ip.h>
> > +#include <rte_malloc.h>
> > +#include <rte_mbuf.h>
> > +#include <rte_mempool.h>
> > +#include <rte_net.h>
> > +
> > +#include "ethdev_rx_priv.h"
> > +#include "kernel_recv_priv.h"
> > +#include "node_private.h"
> > +
> > +static struct kernel_recv_node_main kernel_recv_main;
> 
> Try to remove global varible.
> 
> > +#ifndef __INCLUDE_KERNEL_RECV_PRIV_H__
> 
> No need to add INCLUDE
> 
> > +#define __INCLUDE_KERNEL_RECV_PRIV_H__
> > +
> > +#define KERN_RECV_CACHE_COUNT 64
> > +
> > +typedef struct kernel_recv_info {
> > +       struct rte_mbuf *rx_bufs[KERN_RECV_CACHE_COUNT];
> > +       uint16_t cls_next;
> > +       uint16_t idx;
> > +       uint16_t cnt;
> > +       int sock;
> > +} kernel_recv_info_t;
> > +
> > +/**
> > + * @internal
> > + *
> > + * Kernel Recv node context structure.
> 
> Across the patch series, no need for Doxgen comments for internal
> functions.
Sure Jerin.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-05-30  7:34         ` Jerin Jacob
@ 2023-06-01  2:44           ` Vamsi Krishna Attunuru
  2023-07-20 15:52             ` Rakesh Kudurumalla
  0 siblings, 1 reply; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-06-01  2:44 UTC (permalink / raw)
  To: Jerin Jacob
  Cc: Yan, Zhirun, dev, thomas, Jerin Jacob Kollanukkaran,
	Nithin Kumar Dabilpuram, Liang, Cunming, Wang, Haiyue,
	Sunil Kumar Kori



> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Tuesday, May 30, 2023 1:05 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>;
> Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>; Sunil
> Kumar Kori <skori@marvell.com>
> Subject: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Mon, May 22, 2023 at 12:37 PM Vamsi Krishna Attunuru
> <vattunuru@marvell.com> wrote:
> 
> > > > +static int
> > > > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id) {
> > > > +   int ret = 0;
> > > > +
> > > > +   num_patterns = 0;
> > > > +
> > > > +   if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> 
> 
> I think, if we need to extend the C code for adding new use case, then it will
> not scale.
> IMO, We should look at more runtime and file based interface.
> Something like https://urldefense.proofpoint.com/v2/url?u=https-
> 3A__github.com_DPDK_dpdk_blob_main_examples_ip-
> 5Fpipeline_examples_l2fwd.cli&d=DwIFaQ&c=nKjWec2b6R0mOyPaz7xtfQ&r
> =WllrYaumVkxaWjgKto6E_rtDQshhIhik2jkvzFyRhW8&m=__0iDgw_2lXr0YyyV
> pN3e1Ma9M5SyYMs1pUKnOgvix6u5jfs6MSprWyUh-
> sCmTDw&s=janRFWyPd7Ma3bCIvlVW1YqkAS_U9ouMGNfP1x98pmQ&e=
> In nutshell,
> 1) File based interface to kick-start the valid use case enablement
> 2) Less logic in C code and everything should be driven from config file
> 3) Allow runtime change. examples/ip_pipeline provides telent interface to
> update . Similar concept can be followed.
> 
> I think, we should push the app for next release. Not on this release.
> Sorry for reviewing late.
Sure, agree with your new proposal. I will drop this patch for this release.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes
  2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
                     ` (3 preceding siblings ...)
  2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
@ 2023-06-02 16:22   ` Vamsi Attunuru
  2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
                       ` (4 more replies)
  4 siblings, 5 replies; 182+ messages in thread
From: Vamsi Attunuru @ 2023-06-02 16:22 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram, zhirun.yan

This patch set introduces two new nodes to transmit & receive packets
from kernel. This nodes can be used for any exception path handling or
to forward any control plane traffic to kernel or receive from kernel stack.

V3:
* Address review comments
* Drop test-graph application from the patch set

V2:
* Handle error checks in testgraph application
* Extend supported test node patterns
* Fix warnings

Vamsi Attunuru (3):
  node/kernel_tx: support packet transmit to kernel
  node/kernel_rx: support receiving packets from kernel
  node/ethdev_rx: remove hardcoded node next details

 doc/guides/prog_guide/graph_lib.rst |  17 ++
 lib/node/ethdev_ctrl.c              |   1 +
 lib/node/ethdev_rx.c                |   3 -
 lib/node/kernel_rx.c                | 276 ++++++++++++++++++++++++++++
 lib/node/kernel_rx_priv.h           |  48 +++++
 lib/node/kernel_tx.c                | 122 ++++++++++++
 lib/node/kernel_tx_priv.h           |  16 ++
 lib/node/meson.build                |   2 +
 8 files changed, 482 insertions(+), 3 deletions(-)
 create mode 100644 lib/node/kernel_rx.c
 create mode 100644 lib/node/kernel_rx_priv.h
 create mode 100644 lib/node/kernel_tx.c
 create mode 100644 lib/node/kernel_tx_priv.h

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
@ 2023-06-02 16:22     ` Vamsi Attunuru
  2023-06-05 12:47       ` Nithin Dabilpuram
  2023-06-02 16:22     ` [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel Vamsi Attunuru
                       ` (3 subsequent siblings)
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-06-02 16:22 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram, zhirun.yan

Patch adds a node to transmit the packets to kernel over
a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |   9 ++
 lib/node/kernel_tx.c                | 122 ++++++++++++++++++++++++++++
 lib/node/kernel_tx_priv.h           |  16 ++++
 lib/node/meson.build                |   1 +
 4 files changed, 148 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index 1cfdc86433..fa22b014f3 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -392,3 +392,12 @@ null
 ~~~~
 This node ignores the set of objects passed to it and reports that all are
 processed.
+
+kernel_tx
+~~~~~~~~~
+This node is an exit node that forwards the packets to kernel. It will be used
+to forward any control plane traffic to kernel stack from DPDK. It uses a raw
+socket interface to transmit the packets, it uses the packet's destination
+IP address in sockaddr_in address structure and ``sendto`` function to send
+data on the raw socket. Aftering sending the burst of packets to kernel,
+this node free up the packet buffers.
diff --git a/lib/node/kernel_tx.c b/lib/node/kernel_tx.c
new file mode 100644
index 0000000000..27d1808c71
--- /dev/null
+++ b/lib/node/kernel_tx.c
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+
+#include "kernel_tx_priv.h"
+#include "node_private.h"
+
+static __rte_always_inline void
+kernel_tx_process_mbuf(struct rte_node *node, struct rte_mbuf **mbufs, uint16_t cnt)
+{
+	kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
+	struct sockaddr_in sin = {0};
+	struct rte_ipv4_hdr *ip4;
+	size_t len;
+	char *buf;
+	int i;
+
+	for (i = 0; i < cnt; i++) {
+		ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
+		len = rte_pktmbuf_data_len(mbufs[i]);
+		buf = (char *)ip4;
+
+		sin.sin_family = AF_INET;
+		sin.sin_port = 0;
+		sin.sin_addr.s_addr = ip4->dst_addr;
+
+		if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+			node_err("kernel_tx", "Unable to send packets: %s\n", strerror(errno));
+	}
+}
+
+static uint16_t
+kernel_tx_node_process(struct rte_graph *graph __rte_unused, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
+	uint16_t obj_left = nb_objs;
+
+#define PREFETCH_CNT 4
+
+	while (obj_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]->l2_len));
+		rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *, pkts[7]->l2_len));
+
+		kernel_tx_process_mbuf(node, pkts, PREFETCH_CNT);
+
+		obj_left -= PREFETCH_CNT;
+		pkts += PREFETCH_CNT;
+	}
+
+	while (obj_left > 0) {
+		kernel_tx_process_mbuf(node, pkts, 1);
+
+		obj_left--;
+		pkts++;
+	}
+
+	rte_pktmbuf_free_bulk((struct rte_mbuf **)objs, nb_objs);
+
+	return nb_objs;
+}
+
+static int
+kernel_tx_node_init(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
+
+	ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (ctx->sock < 0)
+		node_err("kernel_tx", "Unable to open RAW socket\n");
+
+	return 0;
+}
+
+static void
+kernel_tx_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
+
+	if (ctx->sock >= 0) {
+		close(ctx->sock);
+		ctx->sock = -1;
+	}
+}
+
+static struct rte_node_register kernel_tx_node_base = {
+	.process = kernel_tx_node_process,
+	.name = "kernel_tx",
+
+	.init = kernel_tx_node_init,
+	.fini = kernel_tx_node_fini,
+
+	.nb_edges = 0,
+};
+
+struct rte_node_register *
+kernel_tx_node_get(void)
+{
+	return &kernel_tx_node_base;
+}
+
+RTE_NODE_REGISTER(kernel_tx_node_base);
diff --git a/lib/node/kernel_tx_priv.h b/lib/node/kernel_tx_priv.h
new file mode 100644
index 0000000000..6fd8a4f2af
--- /dev/null
+++ b/lib/node/kernel_tx_priv.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __KERNEL_TX_PRIV_H__
+#define __KERNEL_TX_PRIV_H__
+
+/* kernel_tx node context structure. */
+typedef struct kernel_tx_node_ctx {
+	int sock;
+} kernel_tx_node_ctx_t;
+
+/* Get the pointer to kernel_tx node register structure */
+struct rte_node_register *kernel_tx_node_get(void);
+
+#endif /* __KERNEL_TX_PRIV_H__ */
diff --git a/lib/node/meson.build b/lib/node/meson.build
index dbdf673c86..0520be23ff 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'ethdev_tx.c',
         'ip4_lookup.c',
         'ip4_rewrite.c',
+        'kernel_tx.c',
         'log.c',
         'null.c',
         'pkt_cls.c',
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
  2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
@ 2023-06-02 16:22     ` Vamsi Attunuru
  2023-06-05 12:50       ` Nithin Dabilpuram
  2023-06-02 16:22     ` [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details Vamsi Attunuru
                       ` (2 subsequent siblings)
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-06-02 16:22 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram, zhirun.yan

Adds a node to receive packets from kernel over
a raw socket.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 doc/guides/prog_guide/graph_lib.rst |   8 +
 lib/node/kernel_rx.c                | 276 ++++++++++++++++++++++++++++
 lib/node/kernel_rx_priv.h           |  48 +++++
 lib/node/meson.build                |   1 +
 4 files changed, 333 insertions(+)

diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index fa22b014f3..4b05bcee3c 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -401,3 +401,11 @@ socket interface to transmit the packets, it uses the packet's destination
 IP address in sockaddr_in address structure and ``sendto`` function to send
 data on the raw socket. Aftering sending the burst of packets to kernel,
 this node free up the packet buffers.
+
+kernel_rx
+~~~~~~~~~
+This node is a source node which receives packets from kernel and forwards to
+any of the intermediate nodes. It uses the raw socket interface to receive
+packets from kernel. Uses ``poll`` function to poll on the socket fd for
+``POLLIN`` events to read the packets from raw socket to stream buffer and does
+``rte_node_next_stream_move()`` when there are received packets.
diff --git a/lib/node/kernel_rx.c b/lib/node/kernel_rx.c
new file mode 100644
index 0000000000..2dba7c8cc7
--- /dev/null
+++ b/lib/node/kernel_rx.c
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <rte_debug.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_ip.h>
+#include <rte_malloc.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_net.h>
+
+#include "ethdev_rx_priv.h"
+#include "kernel_rx_priv.h"
+#include "node_private.h"
+
+static inline struct rte_mbuf *
+alloc_rx_mbuf(kernel_rx_node_ctx_t *ctx)
+{
+	kernel_rx_info_t *rx = ctx->recv_info;
+
+	if (rx->idx >= rx->cnt) {
+		uint16_t cnt;
+
+		rx->idx = 0;
+		rx->cnt = 0;
+
+		cnt = rte_pktmbuf_alloc_bulk(ctx->pktmbuf_pool, rx->rx_bufs, KERN_RX_CACHE_COUNT);
+		if (cnt <= 0)
+			return NULL;
+
+		rx->cnt = cnt;
+	}
+
+	return rx->rx_bufs[rx->idx++];
+}
+
+static inline void
+mbuf_update(struct rte_mbuf **mbufs, uint16_t nb_pkts)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	int i;
+
+	for (i = 0; i < nb_pkts; i++) {
+		m = mbufs[i];
+
+		m->packet_type = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+		m->ol_flags = 0;
+		m->tx_offload = 0;
+
+		m->l2_len = hdr_lens.l2_len;
+		m->l3_len = hdr_lens.l3_len;
+		m->l4_len = hdr_lens.l4_len;
+	}
+}
+
+static uint16_t
+recv_pkt_parse(void **objs, uint16_t nb_pkts)
+{
+	uint16_t pkts_left = nb_pkts;
+	struct rte_mbuf **pkts;
+	int i;
+
+	pkts = (struct rte_mbuf **)objs;
+
+	if (pkts_left >= 4) {
+		for (i = 0; i < 4; i++)
+			rte_prefetch0(rte_pktmbuf_mtod(pkts[i], void *));
+	}
+
+	while (pkts_left >= 12) {
+		/* Prefetch next-next mbufs */
+		rte_prefetch0(pkts[8]);
+		rte_prefetch0(pkts[9]);
+		rte_prefetch0(pkts[10]);
+		rte_prefetch0(pkts[11]);
+
+		/* Prefetch next mbuf data */
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[4], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[5], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[6], void *));
+		rte_prefetch0(rte_pktmbuf_mtod(pkts[7], void *));
+
+		/* Extract ptype of mbufs */
+		mbuf_update(pkts, 4);
+
+		pkts += 4;
+		pkts_left -= 4;
+	}
+
+	if (pkts_left > 0)
+		mbuf_update(pkts, pkts_left);
+
+	return nb_pkts;
+}
+
+static uint16_t
+kernel_rx_node_do(struct rte_graph *graph, struct rte_node *node, kernel_rx_node_ctx_t *ctx)
+{
+	kernel_rx_info_t *rx;
+	uint16_t next_index;
+	int fd;
+
+	rx = ctx->recv_info;
+	next_index = rx->node_next;
+
+	fd = rx->sock;
+	if (fd > 0) {
+		struct rte_mbuf **mbufs;
+		uint16_t len = 0, count = 0;
+		int nb_cnt, i;
+
+		nb_cnt = (node->size >= RTE_GRAPH_BURST_SIZE) ? RTE_GRAPH_BURST_SIZE : node->size;
+
+		mbufs = (struct rte_mbuf **)node->objs;
+		for (i = 0; i < nb_cnt; i++) {
+			struct rte_mbuf *m = alloc_rx_mbuf(ctx);
+
+			if (!m)
+				break;
+
+			len = read(fd, rte_pktmbuf_mtod(m, char *), rte_pktmbuf_tailroom(m));
+			if (len == 0 || len == 0xFFFF) {
+				rte_pktmbuf_free(m);
+				if (rx->idx <= 0)
+					node_dbg("kernel_rx", "rx_mbuf array is empty\n");
+				rx->idx--;
+				break;
+			}
+			*mbufs++ = m;
+
+			m->port = node->id;
+			rte_pktmbuf_data_len(m) = len;
+
+			count++;
+		}
+
+		if (count) {
+			recv_pkt_parse(node->objs, count);
+			node->idx = count;
+
+			/* Enqueue to next node */
+			rte_node_next_stream_move(graph, node, next_index);
+		}
+
+		return count;
+	}
+
+	return 0;
+}
+
+static uint16_t
+kernel_rx_node_process(struct rte_graph *graph, struct rte_node *node, void **objs,
+			 uint16_t nb_objs)
+{
+	kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
+	int fd;
+
+	RTE_SET_USED(objs);
+	RTE_SET_USED(nb_objs);
+
+	if (!ctx)
+		return 0;
+
+	fd = ctx->recv_info->sock;
+	if (fd > 0) {
+		struct pollfd fds = {.fd = fd, .events = POLLIN};
+
+		if (poll(&fds, 1, 0) > 0) {
+			if (fds.revents & POLLIN)
+				return kernel_rx_node_do(graph, node, ctx);
+		}
+	}
+
+	return 0;
+}
+
+static int
+kernel_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+	struct kernel_rx_node_main *rx_node_main = kernel_rx_node_data_get();
+	kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
+	kernel_rx_node_elem_t *elem = rx_node_main->head;
+	kernel_rx_info_t *recv_info;
+	int sock;
+
+	while (elem) {
+		if (elem->nid == node->id) {
+			/* Update node specific context */
+			memcpy(ctx, &elem->ctx, sizeof(kernel_rx_node_ctx_t));
+			break;
+		}
+		elem = elem->next;
+	}
+
+	RTE_VERIFY(elem != NULL);
+
+	if (ctx->pktmbuf_pool == NULL) {
+		node_err("kernel_rx", "Invalid mbuf pool on graph %s\n", graph->name);
+		return -EINVAL;
+	}
+
+	recv_info = rte_zmalloc_socket("kernel_rx_info", sizeof(kernel_rx_info_t),
+				       RTE_CACHE_LINE_SIZE, graph->socket);
+	if (!recv_info) {
+		node_err("kernel_rx", "Kernel recv_info is NULL\n");
+		return -ENOMEM;
+	}
+
+	sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	if (sock < 0) {
+		node_err("kernel_rx", "Unable to open RAW socket\n");
+		return sock;
+	}
+
+	recv_info->sock = sock;
+	ctx->recv_info = recv_info;
+
+	return 0;
+}
+
+static void
+kernel_rx_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
+{
+	kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
+
+	if (ctx->recv_info) {
+		close(ctx->recv_info->sock);
+		ctx->recv_info->sock = -1;
+		rte_free(ctx->recv_info);
+	}
+
+	ctx->recv_info = NULL;
+}
+
+struct kernel_rx_node_main *
+kernel_rx_node_data_get(void)
+{
+	static struct kernel_rx_node_main kernel_rx_main;
+
+	return &kernel_rx_main;
+}
+
+static struct rte_node_register kernel_rx_node_base = {
+	.process = kernel_rx_node_process,
+	.flags = RTE_NODE_SOURCE_F,
+	.name = "kernel_rx",
+
+	.init = kernel_rx_node_init,
+	.fini = kernel_rx_node_fini,
+
+	.nb_edges = KERNEL_RX_NEXT_MAX,
+	.next_nodes = {
+			[KERNEL_RX_NEXT_PKT_CLS] = "pkt_cls",
+			[KERNEL_RX_NEXT_IP4_LOOKUP] = "ip4_lookup",
+	},
+};
+
+struct rte_node_register *
+kernel_rx_node_get(void)
+{
+	return &kernel_rx_node_base;
+}
+
+RTE_NODE_REGISTER(kernel_rx_node_base);
diff --git a/lib/node/kernel_rx_priv.h b/lib/node/kernel_rx_priv.h
new file mode 100644
index 0000000000..f1aa2344d7
--- /dev/null
+++ b/lib/node/kernel_rx_priv.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell International Ltd.
+ */
+
+#ifndef __KERNEL_RX_PRIV_H__
+#define __KERNEL_RX_PRIV_H__
+
+#define KERN_RX_CACHE_COUNT 64
+
+typedef struct kernel_rx_info {
+	struct rte_mbuf *rx_bufs[KERN_RX_CACHE_COUNT];
+	uint16_t node_next;
+	uint16_t idx;
+	uint16_t cnt;
+	int sock;
+} kernel_rx_info_t;
+
+/* kernel_rx node context structure */
+typedef struct kernel_rx_node_ctx {
+	struct rte_mempool *pktmbuf_pool;
+	kernel_rx_info_t *recv_info;
+} kernel_rx_node_ctx_t;
+
+/* kernel_rx node list element structure */
+typedef struct kernel_rx_node_elem {
+	struct kernel_rx_node_elem *next; /* Pointer to the next node element. */
+	struct kernel_rx_node_ctx ctx;    /* kernel_rx node context. */
+	rte_node_t nid;			  /* Node identifier of the kernel_rx node. */
+} kernel_rx_node_elem_t;
+
+enum kernel_rx_next_nodes {
+	KERNEL_RX_NEXT_PKT_CLS,
+	KERNEL_RX_NEXT_IP4_LOOKUP,
+	KERNEL_RX_NEXT_MAX,
+};
+
+/* kernel_rx node main structure */
+struct kernel_rx_node_main {
+	kernel_rx_node_elem_t *head; /* Pointer to the head node element. */
+};
+
+/* Get the pointer of kernel_rx node data */
+struct kernel_rx_node_main *kernel_rx_node_data_get(void);
+
+/* Get the pointer of kernel_rx node register structure */
+struct rte_node_register *kernel_rx_node_get(void);
+
+#endif /* __KERNEL_RX_PRIV_H__ */
diff --git a/lib/node/meson.build b/lib/node/meson.build
index 0520be23ff..1f04fd1d7e 100644
--- a/lib/node/meson.build
+++ b/lib/node/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'ethdev_tx.c',
         'ip4_lookup.c',
         'ip4_rewrite.c',
+        'kernel_rx.c',
         'kernel_tx.c',
         'log.c',
         'null.c',
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
  2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
  2023-06-02 16:22     ` [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel Vamsi Attunuru
@ 2023-06-02 16:22     ` Vamsi Attunuru
  2023-06-05 12:51       ` Nithin Dabilpuram
  2023-06-12 16:12     ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Krishna Attunuru
  2023-06-12 19:31     ` Thomas Monjalon
  4 siblings, 1 reply; 182+ messages in thread
From: Vamsi Attunuru @ 2023-06-02 16:22 UTC (permalink / raw)
  To: dev, thomas, jerinj; +Cc: vattunuru, ndabilpuram, zhirun.yan

For ethdev_rx node, node_next details can be populated
during node cloning time and same gets assigned to
node context structure during node initialization.

Patch removes overriding node_next details in node
init().

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
---
 lib/node/ethdev_ctrl.c | 1 +
 lib/node/ethdev_rx.c   | 3 ---
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/lib/node/ethdev_ctrl.c b/lib/node/ethdev_ctrl.c
index 37df0431b8..496f791cee 100644
--- a/lib/node/ethdev_ctrl.c
+++ b/lib/node/ethdev_ctrl.c
@@ -82,6 +82,7 @@ rte_node_eth_config(struct rte_node_ethdev_config *conf, uint16_t nb_confs,
 			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
 			elem->ctx.port_id = port_id;
 			elem->ctx.queue_id = j;
+			elem->ctx.cls_next = ETHDEV_RX_NEXT_PKT_CLS;
 			elem->nid = id;
 			elem->next = rx_node_data->head;
 			rx_node_data->head = elem;
diff --git a/lib/node/ethdev_rx.c b/lib/node/ethdev_rx.c
index a19237b42f..d131034991 100644
--- a/lib/node/ethdev_rx.c
+++ b/lib/node/ethdev_rx.c
@@ -194,8 +194,6 @@ ethdev_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
 
 	RTE_VERIFY(elem != NULL);
 
-	ctx->cls_next = ETHDEV_RX_NEXT_PKT_CLS;
-
 	/* Check and setup ptype */
 	return ethdev_ptype_setup(ctx->port_id, ctx->queue_id);
 }
@@ -215,7 +213,6 @@ static struct rte_node_register ethdev_rx_node_base = {
 
 	.nb_edges = ETHDEV_RX_NEXT_MAX,
 	.next_nodes = {
-		/* Default pkt classification node */
 		[ETHDEV_RX_NEXT_PKT_CLS] = "pkt_cls",
 		[ETHDEV_RX_NEXT_IP4_LOOKUP] = "ip4_lookup",
 	},
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel
  2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
@ 2023-06-05 12:47       ` Nithin Dabilpuram
  2023-06-12 19:32         ` Thomas Monjalon
  0 siblings, 1 reply; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-06-05 12:47 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, thomas, jerinj, ndabilpuram, zhirun.yan

Acked-by: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Jun 2, 2023 at 9:52 PM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> Patch adds a node to transmit the packets to kernel over
> a raw socket.
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  doc/guides/prog_guide/graph_lib.rst |   9 ++
>  lib/node/kernel_tx.c                | 122 ++++++++++++++++++++++++++++
>  lib/node/kernel_tx_priv.h           |  16 ++++
>  lib/node/meson.build                |   1 +
>  4 files changed, 148 insertions(+)
>
> diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
> index 1cfdc86433..fa22b014f3 100644
> --- a/doc/guides/prog_guide/graph_lib.rst
> +++ b/doc/guides/prog_guide/graph_lib.rst
> @@ -392,3 +392,12 @@ null
>  ~~~~
>  This node ignores the set of objects passed to it and reports that all are
>  processed.
> +
> +kernel_tx
> +~~~~~~~~~
> +This node is an exit node that forwards the packets to kernel. It will be used
> +to forward any control plane traffic to kernel stack from DPDK. It uses a raw
> +socket interface to transmit the packets, it uses the packet's destination
> +IP address in sockaddr_in address structure and ``sendto`` function to send
> +data on the raw socket. Aftering sending the burst of packets to kernel,
> +this node free up the packet buffers.
> diff --git a/lib/node/kernel_tx.c b/lib/node/kernel_tx.c
> new file mode 100644
> index 0000000000..27d1808c71
> --- /dev/null
> +++ b/lib/node/kernel_tx.c
> @@ -0,0 +1,122 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fcntl.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include <rte_debug.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_ip.h>
> +
> +#include "kernel_tx_priv.h"
> +#include "node_private.h"
> +
> +static __rte_always_inline void
> +kernel_tx_process_mbuf(struct rte_node *node, struct rte_mbuf **mbufs, uint16_t cnt)
> +{
> +       kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
> +       struct sockaddr_in sin = {0};
> +       struct rte_ipv4_hdr *ip4;
> +       size_t len;
> +       char *buf;
> +       int i;
> +
> +       for (i = 0; i < cnt; i++) {
> +               ip4 = rte_pktmbuf_mtod(mbufs[i], struct rte_ipv4_hdr *);
> +               len = rte_pktmbuf_data_len(mbufs[i]);
> +               buf = (char *)ip4;
> +
> +               sin.sin_family = AF_INET;
> +               sin.sin_port = 0;
> +               sin.sin_addr.s_addr = ip4->dst_addr;
> +
> +               if (sendto(ctx->sock, buf, len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
> +                       node_err("kernel_tx", "Unable to send packets: %s\n", strerror(errno));
> +       }
> +}
> +
> +static uint16_t
> +kernel_tx_node_process(struct rte_graph *graph __rte_unused, struct rte_node *node, void **objs,
> +                        uint16_t nb_objs)
> +{
> +       struct rte_mbuf **pkts = (struct rte_mbuf **)objs;
> +       uint16_t obj_left = nb_objs;
> +
> +#define PREFETCH_CNT 4
> +
> +       while (obj_left >= 12) {
> +               /* Prefetch next-next mbufs */
> +               rte_prefetch0(pkts[8]);
> +               rte_prefetch0(pkts[9]);
> +               rte_prefetch0(pkts[10]);
> +               rte_prefetch0(pkts[11]);
> +
> +               /* Prefetch next mbuf data */
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[4], void *, pkts[4]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[5], void *, pkts[5]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[6], void *, pkts[6]->l2_len));
> +               rte_prefetch0(rte_pktmbuf_mtod_offset(pkts[7], void *, pkts[7]->l2_len));
> +
> +               kernel_tx_process_mbuf(node, pkts, PREFETCH_CNT);
> +
> +               obj_left -= PREFETCH_CNT;
> +               pkts += PREFETCH_CNT;
> +       }
> +
> +       while (obj_left > 0) {
> +               kernel_tx_process_mbuf(node, pkts, 1);
> +
> +               obj_left--;
> +               pkts++;
> +       }
> +
> +       rte_pktmbuf_free_bulk((struct rte_mbuf **)objs, nb_objs);
> +
> +       return nb_objs;
> +}
> +
> +static int
> +kernel_tx_node_init(const struct rte_graph *graph __rte_unused, struct rte_node *node)
> +{
> +       kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
> +
> +       ctx->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
> +       if (ctx->sock < 0)
> +               node_err("kernel_tx", "Unable to open RAW socket\n");
> +
> +       return 0;
> +}
> +
> +static void
> +kernel_tx_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
> +{
> +       kernel_tx_node_ctx_t *ctx = (kernel_tx_node_ctx_t *)node->ctx;
> +
> +       if (ctx->sock >= 0) {
> +               close(ctx->sock);
> +               ctx->sock = -1;
> +       }
> +}
> +
> +static struct rte_node_register kernel_tx_node_base = {
> +       .process = kernel_tx_node_process,
> +       .name = "kernel_tx",
> +
> +       .init = kernel_tx_node_init,
> +       .fini = kernel_tx_node_fini,
> +
> +       .nb_edges = 0,
> +};
> +
> +struct rte_node_register *
> +kernel_tx_node_get(void)
> +{
> +       return &kernel_tx_node_base;
> +}
> +
> +RTE_NODE_REGISTER(kernel_tx_node_base);
> diff --git a/lib/node/kernel_tx_priv.h b/lib/node/kernel_tx_priv.h
> new file mode 100644
> index 0000000000..6fd8a4f2af
> --- /dev/null
> +++ b/lib/node/kernel_tx_priv.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef __KERNEL_TX_PRIV_H__
> +#define __KERNEL_TX_PRIV_H__
> +
> +/* kernel_tx node context structure. */
> +typedef struct kernel_tx_node_ctx {
> +       int sock;
> +} kernel_tx_node_ctx_t;
> +
> +/* Get the pointer to kernel_tx node register structure */
> +struct rte_node_register *kernel_tx_node_get(void);
> +
> +#endif /* __KERNEL_TX_PRIV_H__ */
> diff --git a/lib/node/meson.build b/lib/node/meson.build
> index dbdf673c86..0520be23ff 100644
> --- a/lib/node/meson.build
> +++ b/lib/node/meson.build
> @@ -13,6 +13,7 @@ sources = files(
>          'ethdev_tx.c',
>          'ip4_lookup.c',
>          'ip4_rewrite.c',
> +        'kernel_tx.c',
>          'log.c',
>          'null.c',
>          'pkt_cls.c',
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel
  2023-06-02 16:22     ` [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel Vamsi Attunuru
@ 2023-06-05 12:50       ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-06-05 12:50 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, thomas, jerinj, ndabilpuram, zhirun.yan

Acked-by: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Jun 2, 2023 at 9:52 PM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> Adds a node to receive packets from kernel over
> a raw socket.
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  doc/guides/prog_guide/graph_lib.rst |   8 +
>  lib/node/kernel_rx.c                | 276 ++++++++++++++++++++++++++++
>  lib/node/kernel_rx_priv.h           |  48 +++++
>  lib/node/meson.build                |   1 +
>  4 files changed, 333 insertions(+)
>
> diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
> index fa22b014f3..4b05bcee3c 100644
> --- a/doc/guides/prog_guide/graph_lib.rst
> +++ b/doc/guides/prog_guide/graph_lib.rst
> @@ -401,3 +401,11 @@ socket interface to transmit the packets, it uses the packet's destination
>  IP address in sockaddr_in address structure and ``sendto`` function to send
>  data on the raw socket. Aftering sending the burst of packets to kernel,
>  this node free up the packet buffers.
> +
> +kernel_rx
> +~~~~~~~~~
> +This node is a source node which receives packets from kernel and forwards to
> +any of the intermediate nodes. It uses the raw socket interface to receive
> +packets from kernel. Uses ``poll`` function to poll on the socket fd for
> +``POLLIN`` events to read the packets from raw socket to stream buffer and does
> +``rte_node_next_stream_move()`` when there are received packets.
> diff --git a/lib/node/kernel_rx.c b/lib/node/kernel_rx.c
> new file mode 100644
> index 0000000000..2dba7c8cc7
> --- /dev/null
> +++ b/lib/node/kernel_rx.c
> @@ -0,0 +1,276 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#include <fcntl.h>
> +#include <poll.h>
> +#include <stdlib.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
> +#include <unistd.h>
> +
> +#include <rte_debug.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_ip.h>
> +#include <rte_malloc.h>
> +#include <rte_mbuf.h>
> +#include <rte_mempool.h>
> +#include <rte_net.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "kernel_rx_priv.h"
> +#include "node_private.h"
> +
> +static inline struct rte_mbuf *
> +alloc_rx_mbuf(kernel_rx_node_ctx_t *ctx)
> +{
> +       kernel_rx_info_t *rx = ctx->recv_info;
> +
> +       if (rx->idx >= rx->cnt) {
> +               uint16_t cnt;
> +
> +               rx->idx = 0;
> +               rx->cnt = 0;
> +
> +               cnt = rte_pktmbuf_alloc_bulk(ctx->pktmbuf_pool, rx->rx_bufs, KERN_RX_CACHE_COUNT);
> +               if (cnt <= 0)
> +                       return NULL;
> +
> +               rx->cnt = cnt;
> +       }
> +
> +       return rx->rx_bufs[rx->idx++];
> +}
> +
> +static inline void
> +mbuf_update(struct rte_mbuf **mbufs, uint16_t nb_pkts)
> +{
> +       struct rte_net_hdr_lens hdr_lens;
> +       struct rte_mbuf *m;
> +       int i;
> +
> +       for (i = 0; i < nb_pkts; i++) {
> +               m = mbufs[i];
> +
> +               m->packet_type = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
> +
> +               m->ol_flags = 0;
> +               m->tx_offload = 0;
> +
> +               m->l2_len = hdr_lens.l2_len;
> +               m->l3_len = hdr_lens.l3_len;
> +               m->l4_len = hdr_lens.l4_len;
> +       }
> +}
> +
> +static uint16_t
> +recv_pkt_parse(void **objs, uint16_t nb_pkts)
> +{
> +       uint16_t pkts_left = nb_pkts;
> +       struct rte_mbuf **pkts;
> +       int i;
> +
> +       pkts = (struct rte_mbuf **)objs;
> +
> +       if (pkts_left >= 4) {
> +               for (i = 0; i < 4; i++)
> +                       rte_prefetch0(rte_pktmbuf_mtod(pkts[i], void *));
> +       }
> +
> +       while (pkts_left >= 12) {
> +               /* Prefetch next-next mbufs */
> +               rte_prefetch0(pkts[8]);
> +               rte_prefetch0(pkts[9]);
> +               rte_prefetch0(pkts[10]);
> +               rte_prefetch0(pkts[11]);
> +
> +               /* Prefetch next mbuf data */
> +               rte_prefetch0(rte_pktmbuf_mtod(pkts[4], void *));
> +               rte_prefetch0(rte_pktmbuf_mtod(pkts[5], void *));
> +               rte_prefetch0(rte_pktmbuf_mtod(pkts[6], void *));
> +               rte_prefetch0(rte_pktmbuf_mtod(pkts[7], void *));
> +
> +               /* Extract ptype of mbufs */
> +               mbuf_update(pkts, 4);
> +
> +               pkts += 4;
> +               pkts_left -= 4;
> +       }
> +
> +       if (pkts_left > 0)
> +               mbuf_update(pkts, pkts_left);
> +
> +       return nb_pkts;
> +}
> +
> +static uint16_t
> +kernel_rx_node_do(struct rte_graph *graph, struct rte_node *node, kernel_rx_node_ctx_t *ctx)
> +{
> +       kernel_rx_info_t *rx;
> +       uint16_t next_index;
> +       int fd;
> +
> +       rx = ctx->recv_info;
> +       next_index = rx->node_next;
> +
> +       fd = rx->sock;
> +       if (fd > 0) {
> +               struct rte_mbuf **mbufs;
> +               uint16_t len = 0, count = 0;
> +               int nb_cnt, i;
> +
> +               nb_cnt = (node->size >= RTE_GRAPH_BURST_SIZE) ? RTE_GRAPH_BURST_SIZE : node->size;
> +
> +               mbufs = (struct rte_mbuf **)node->objs;
> +               for (i = 0; i < nb_cnt; i++) {
> +                       struct rte_mbuf *m = alloc_rx_mbuf(ctx);
> +
> +                       if (!m)
> +                               break;
> +
> +                       len = read(fd, rte_pktmbuf_mtod(m, char *), rte_pktmbuf_tailroom(m));
> +                       if (len == 0 || len == 0xFFFF) {
> +                               rte_pktmbuf_free(m);
> +                               if (rx->idx <= 0)
> +                                       node_dbg("kernel_rx", "rx_mbuf array is empty\n");
> +                               rx->idx--;
> +                               break;
> +                       }
> +                       *mbufs++ = m;
> +
> +                       m->port = node->id;
> +                       rte_pktmbuf_data_len(m) = len;
> +
> +                       count++;
> +               }
> +
> +               if (count) {
> +                       recv_pkt_parse(node->objs, count);
> +                       node->idx = count;
> +
> +                       /* Enqueue to next node */
> +                       rte_node_next_stream_move(graph, node, next_index);
> +               }
> +
> +               return count;
> +       }
> +
> +       return 0;
> +}
> +
> +static uint16_t
> +kernel_rx_node_process(struct rte_graph *graph, struct rte_node *node, void **objs,
> +                        uint16_t nb_objs)
> +{
> +       kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
> +       int fd;
> +
> +       RTE_SET_USED(objs);
> +       RTE_SET_USED(nb_objs);
> +
> +       if (!ctx)
> +               return 0;
> +
> +       fd = ctx->recv_info->sock;
> +       if (fd > 0) {
> +               struct pollfd fds = {.fd = fd, .events = POLLIN};
> +
> +               if (poll(&fds, 1, 0) > 0) {
> +                       if (fds.revents & POLLIN)
> +                               return kernel_rx_node_do(graph, node, ctx);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +kernel_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
> +{
> +       struct kernel_rx_node_main *rx_node_main = kernel_rx_node_data_get();
> +       kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
> +       kernel_rx_node_elem_t *elem = rx_node_main->head;
> +       kernel_rx_info_t *recv_info;
> +       int sock;
> +
> +       while (elem) {
> +               if (elem->nid == node->id) {
> +                       /* Update node specific context */
> +                       memcpy(ctx, &elem->ctx, sizeof(kernel_rx_node_ctx_t));
> +                       break;
> +               }
> +               elem = elem->next;
> +       }
> +
> +       RTE_VERIFY(elem != NULL);
> +
> +       if (ctx->pktmbuf_pool == NULL) {
> +               node_err("kernel_rx", "Invalid mbuf pool on graph %s\n", graph->name);
> +               return -EINVAL;
> +       }
> +
> +       recv_info = rte_zmalloc_socket("kernel_rx_info", sizeof(kernel_rx_info_t),
> +                                      RTE_CACHE_LINE_SIZE, graph->socket);
> +       if (!recv_info) {
> +               node_err("kernel_rx", "Kernel recv_info is NULL\n");
> +               return -ENOMEM;
> +       }
> +
> +       sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
> +       if (sock < 0) {
> +               node_err("kernel_rx", "Unable to open RAW socket\n");
> +               return sock;
> +       }
> +
> +       recv_info->sock = sock;
> +       ctx->recv_info = recv_info;
> +
> +       return 0;
> +}
> +
> +static void
> +kernel_rx_node_fini(const struct rte_graph *graph __rte_unused, struct rte_node *node)
> +{
> +       kernel_rx_node_ctx_t *ctx = (kernel_rx_node_ctx_t *)node->ctx;
> +
> +       if (ctx->recv_info) {
> +               close(ctx->recv_info->sock);
> +               ctx->recv_info->sock = -1;
> +               rte_free(ctx->recv_info);
> +       }
> +
> +       ctx->recv_info = NULL;
> +}
> +
> +struct kernel_rx_node_main *
> +kernel_rx_node_data_get(void)
> +{
> +       static struct kernel_rx_node_main kernel_rx_main;
> +
> +       return &kernel_rx_main;
> +}
> +
> +static struct rte_node_register kernel_rx_node_base = {
> +       .process = kernel_rx_node_process,
> +       .flags = RTE_NODE_SOURCE_F,
> +       .name = "kernel_rx",
> +
> +       .init = kernel_rx_node_init,
> +       .fini = kernel_rx_node_fini,
> +
> +       .nb_edges = KERNEL_RX_NEXT_MAX,
> +       .next_nodes = {
> +                       [KERNEL_RX_NEXT_PKT_CLS] = "pkt_cls",
> +                       [KERNEL_RX_NEXT_IP4_LOOKUP] = "ip4_lookup",
> +       },
> +};
> +
> +struct rte_node_register *
> +kernel_rx_node_get(void)
> +{
> +       return &kernel_rx_node_base;
> +}
> +
> +RTE_NODE_REGISTER(kernel_rx_node_base);
> diff --git a/lib/node/kernel_rx_priv.h b/lib/node/kernel_rx_priv.h
> new file mode 100644
> index 0000000000..f1aa2344d7
> --- /dev/null
> +++ b/lib/node/kernel_rx_priv.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2023 Marvell International Ltd.
> + */
> +
> +#ifndef __KERNEL_RX_PRIV_H__
> +#define __KERNEL_RX_PRIV_H__
> +
> +#define KERN_RX_CACHE_COUNT 64
> +
> +typedef struct kernel_rx_info {
> +       struct rte_mbuf *rx_bufs[KERN_RX_CACHE_COUNT];
> +       uint16_t node_next;
> +       uint16_t idx;
> +       uint16_t cnt;
> +       int sock;
> +} kernel_rx_info_t;
> +
> +/* kernel_rx node context structure */
> +typedef struct kernel_rx_node_ctx {
> +       struct rte_mempool *pktmbuf_pool;
> +       kernel_rx_info_t *recv_info;
> +} kernel_rx_node_ctx_t;
> +
> +/* kernel_rx node list element structure */
> +typedef struct kernel_rx_node_elem {
> +       struct kernel_rx_node_elem *next; /* Pointer to the next node element. */
> +       struct kernel_rx_node_ctx ctx;    /* kernel_rx node context. */
> +       rte_node_t nid;                   /* Node identifier of the kernel_rx node. */
> +} kernel_rx_node_elem_t;
> +
> +enum kernel_rx_next_nodes {
> +       KERNEL_RX_NEXT_PKT_CLS,
> +       KERNEL_RX_NEXT_IP4_LOOKUP,
> +       KERNEL_RX_NEXT_MAX,
> +};
> +
> +/* kernel_rx node main structure */
> +struct kernel_rx_node_main {
> +       kernel_rx_node_elem_t *head; /* Pointer to the head node element. */
> +};
> +
> +/* Get the pointer of kernel_rx node data */
> +struct kernel_rx_node_main *kernel_rx_node_data_get(void);
> +
> +/* Get the pointer of kernel_rx node register structure */
> +struct rte_node_register *kernel_rx_node_get(void);
> +
> +#endif /* __KERNEL_RX_PRIV_H__ */
> diff --git a/lib/node/meson.build b/lib/node/meson.build
> index 0520be23ff..1f04fd1d7e 100644
> --- a/lib/node/meson.build
> +++ b/lib/node/meson.build
> @@ -13,6 +13,7 @@ sources = files(
>          'ethdev_tx.c',
>          'ip4_lookup.c',
>          'ip4_rewrite.c',
> +        'kernel_rx.c',
>          'kernel_tx.c',
>          'log.c',
>          'null.c',
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details
  2023-06-02 16:22     ` [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details Vamsi Attunuru
@ 2023-06-05 12:51       ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-06-05 12:51 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, thomas, jerinj, ndabilpuram, zhirun.yan

Acked-by: Nithin Dabilpuram<ndabilpuram@marvell.com>

On Fri, Jun 2, 2023 at 9:52 PM Vamsi Attunuru <vattunuru@marvell.com> wrote:
>
> For ethdev_rx node, node_next details can be populated
> during node cloning time and same gets assigned to
> node context structure during node initialization.
>
> Patch removes overriding node_next details in node
> init().
>
> Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> ---
>  lib/node/ethdev_ctrl.c | 1 +
>  lib/node/ethdev_rx.c   | 3 ---
>  2 files changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/lib/node/ethdev_ctrl.c b/lib/node/ethdev_ctrl.c
> index 37df0431b8..496f791cee 100644
> --- a/lib/node/ethdev_ctrl.c
> +++ b/lib/node/ethdev_ctrl.c
> @@ -82,6 +82,7 @@ rte_node_eth_config(struct rte_node_ethdev_config *conf, uint16_t nb_confs,
>                         memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
>                         elem->ctx.port_id = port_id;
>                         elem->ctx.queue_id = j;
> +                       elem->ctx.cls_next = ETHDEV_RX_NEXT_PKT_CLS;
>                         elem->nid = id;
>                         elem->next = rx_node_data->head;
>                         rx_node_data->head = elem;
> diff --git a/lib/node/ethdev_rx.c b/lib/node/ethdev_rx.c
> index a19237b42f..d131034991 100644
> --- a/lib/node/ethdev_rx.c
> +++ b/lib/node/ethdev_rx.c
> @@ -194,8 +194,6 @@ ethdev_rx_node_init(const struct rte_graph *graph, struct rte_node *node)
>
>         RTE_VERIFY(elem != NULL);
>
> -       ctx->cls_next = ETHDEV_RX_NEXT_PKT_CLS;
> -
>         /* Check and setup ptype */
>         return ethdev_ptype_setup(ctx->port_id, ctx->queue_id);
>  }
> @@ -215,7 +213,6 @@ static struct rte_node_register ethdev_rx_node_base = {
>
>         .nb_edges = ETHDEV_RX_NEXT_MAX,
>         .next_nodes = {
> -               /* Default pkt classification node */
>                 [ETHDEV_RX_NEXT_PKT_CLS] = "pkt_cls",
>                 [ETHDEV_RX_NEXT_IP4_LOOKUP] = "ip4_lookup",
>         },
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
                       ` (2 preceding siblings ...)
  2023-06-02 16:22     ` [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details Vamsi Attunuru
@ 2023-06-12 16:12     ` Vamsi Krishna Attunuru
  2023-06-12 19:31     ` Thomas Monjalon
  4 siblings, 0 replies; 182+ messages in thread
From: Vamsi Krishna Attunuru @ 2023-06-12 16:12 UTC (permalink / raw)
  To: Vamsi Krishna Attunuru, dev, thomas, Jerin Jacob Kollanukkaran
  Cc: Nithin Kumar Dabilpuram, zhirun.yan

Hi Thomas,

This series is acked by node maintainer. Please consider merging.

> -----Original Message-----
> From: Vamsi Attunuru <vattunuru@marvell.com>
> Sent: Friday, June 2, 2023 9:52 PM
> To: dev@dpdk.org; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> <jerinj@marvell.com>
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Nithin Kumar
> Dabilpuram <ndabilpuram@marvell.com>; zhirun.yan@intel.com
> Subject: [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes
> 
> This patch set introduces two new nodes to transmit & receive packets from
> kernel. This nodes can be used for any exception path handling or to forward
> any control plane traffic to kernel or receive from kernel stack.
> 
> V3:
> * Address review comments
> * Drop test-graph application from the patch set
> 
> V2:
> * Handle error checks in testgraph application
> * Extend supported test node patterns
> * Fix warnings
> 
> Vamsi Attunuru (3):
>   node/kernel_tx: support packet transmit to kernel
>   node/kernel_rx: support receiving packets from kernel
>   node/ethdev_rx: remove hardcoded node next details
> 
>  doc/guides/prog_guide/graph_lib.rst |  17 ++
>  lib/node/ethdev_ctrl.c              |   1 +
>  lib/node/ethdev_rx.c                |   3 -
>  lib/node/kernel_rx.c                | 276 ++++++++++++++++++++++++++++
>  lib/node/kernel_rx_priv.h           |  48 +++++
>  lib/node/kernel_tx.c                | 122 ++++++++++++
>  lib/node/kernel_tx_priv.h           |  16 ++
>  lib/node/meson.build                |   2 +
>  8 files changed, 482 insertions(+), 3 deletions(-)  create mode 100644
> lib/node/kernel_rx.c  create mode 100644 lib/node/kernel_rx_priv.h  create
> mode 100644 lib/node/kernel_tx.c  create mode 100644
> lib/node/kernel_tx_priv.h
> 
> --
> 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes
  2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
                       ` (3 preceding siblings ...)
  2023-06-12 16:12     ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Krishna Attunuru
@ 2023-06-12 19:31     ` Thomas Monjalon
  4 siblings, 0 replies; 182+ messages in thread
From: Thomas Monjalon @ 2023-06-12 19:31 UTC (permalink / raw)
  To: Vamsi Attunuru; +Cc: dev, jerinj, ndabilpuram, zhirun.yan

02/06/2023 18:22, Vamsi Attunuru:
> This patch set introduces two new nodes to transmit & receive packets
> from kernel. This nodes can be used for any exception path handling or
> to forward any control plane traffic to kernel or receive from kernel stack.
> 
> V3:
> * Address review comments
> * Drop test-graph application from the patch set
> 
> V2:
> * Handle error checks in testgraph application
> * Extend supported test node patterns
> * Fix warnings
> 
> Vamsi Attunuru (3):
>   node/kernel_tx: support packet transmit to kernel
>   node/kernel_rx: support receiving packets from kernel
>   node/ethdev_rx: remove hardcoded node next details

Applied, thanks.



^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel
  2023-06-05 12:47       ` Nithin Dabilpuram
@ 2023-06-12 19:32         ` Thomas Monjalon
  0 siblings, 0 replies; 182+ messages in thread
From: Thomas Monjalon @ 2023-06-12 19:32 UTC (permalink / raw)
  To: Vamsi Attunuru, Nithin Dabilpuram; +Cc: dev, jerinj, ndabilpuram, zhirun.yan

05/06/2023 14:47, Nithin Dabilpuram:
> Acked-by: Nithin Dabilpuram <ndabilpuram@marvell.com>
> 
> On Fri, Jun 2, 2023 at 9:52 PM Vamsi Attunuru <vattunuru@marvell.com> wrote:
> >
> > Patch adds a node to transmit the packets to kernel over
> > a raw socket.
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
[...]
> > +kernel_tx
> > +~~~~~~~~~
> > +This node is an exit node that forwards the packets to kernel. It will be used
> > +to forward any control plane traffic to kernel stack from DPDK. It uses a raw
> > +socket interface to transmit the packets, it uses the packet's destination
> > +IP address in sockaddr_in address structure and ``sendto`` function to send
> > +data on the raw socket. Aftering sending the burst of packets to kernel,

Typo: Aftering -> After
Fixed on apply.





^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-06-01  2:44           ` [EXT] " Vamsi Krishna Attunuru
@ 2023-07-20 15:52             ` Rakesh Kudurumalla
  2023-07-21  6:48               ` Jerin Jacob
  0 siblings, 1 reply; 182+ messages in thread
From: Rakesh Kudurumalla @ 2023-07-20 15:52 UTC (permalink / raw)
  To: Vamsi Krishna Attunuru, Jerin Jacob, dev
  Cc: Yan, Zhirun, thomas, Jerin Jacob Kollanukkaran,
	Nithin Kumar Dabilpuram, Liang, Cunming, Wang, Haiyue,
	Sunil Kumar Kori



> -----Original Message-----
> From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> Sent: Thursday, June 1, 2023 8:14 AM
> To: Jerin Jacob <jerinjacobk@gmail.com>
> Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>;
> Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>; Sunil
> Kumar Kori <skori@marvell.com>
> Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> 
> 
> 
> > -----Original Message-----
> > From: Jerin Jacob <jerinjacobk@gmail.com>
> > Sent: Tuesday, May 30, 2023 1:05 PM
> > To: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>;
> > Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> > <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>;
> Sunil
> > Kumar Kori <skori@marvell.com>
> > Subject: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> >
> > External Email
> >
> > ----------------------------------------------------------------------
> > On Mon, May 22, 2023 at 12:37 PM Vamsi Krishna Attunuru
> > <vattunuru@marvell.com> wrote:
> >
> > > > > +static int
> > > > > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id) {
> > > > > +   int ret = 0;
> > > > > +
> > > > > +   num_patterns = 0;
> > > > > +
> > > > > +   if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> >
> >
> > I think, if we need to extend the C code for adding new use case, then
> > it will not scale.
> > IMO, We should look at more runtime and file based interface.
> > Something like https://urldefense.proofpoint.com/v2/url?u=https-
> > 3A__github.com_DPDK_dpdk_blob_main_examples_ip-
> >
> 5Fpipeline_examples_l2fwd.cli&d=DwIFaQ&c=nKjWec2b6R0mOyPaz7xtfQ&r
> >
> =WllrYaumVkxaWjgKto6E_rtDQshhIhik2jkvzFyRhW8&m=__0iDgw_2lXr0YyyV
> > pN3e1Ma9M5SyYMs1pUKnOgvix6u5jfs6MSprWyUh-
> > sCmTDw&s=janRFWyPd7Ma3bCIvlVW1YqkAS_U9ouMGNfP1x98pmQ&e=
> > In nutshell,
> > 1) File based interface to kick-start the valid use case enablement
> > 2) Less logic in C code and everything should be driven from config
> > file
> > 3) Allow runtime change. examples/ip_pipeline provides telent
> > interface to update . Similar concept can be followed.
> >
> > I think, we should push the app for next release. Not on this release.
> > Sorry for reviewing late.
> Sure, agree with your new proposal. I will drop this patch for this release.

As discussed we are proposing command based interface for running different
usecases with configuration file as input per usecase. Please review and let us 
know your comments meanwhile we have started implementing v1 with below specification.

Graph application Interface file
Configure Use cases
=============
This section consists of which use cases are needed to be configured and model to be used along with number of coremask 
to run graph on that. Following is the exposed syntax to configure given use case.

Syntax:

graph <usecases> [usecase specific configuration] <model> [model specific configuration]

Where:

usecases: It is comma separated list defining the requested use cases. Example values are -
l3fwd
ipsec
usecase specific configuration: It defines following usecase specific configuration -
burst size (bsz)
timeout (tmo)
coremask
model: It defines the model for dequeuing packets.  Example models are -
run to completion (rtc)
multi core dispatch (mcd)
Global specific configuration
==================
This section consists of device specific configuration which are needed to make a DPDK port usable such as number

of Rx/TX queues, MTU, mempool etc. Along with it consists global network table configuration required for each use case

such as configure IP address to device, arp entries for given IP etc. Supported hardware offloads to be used by 
this use case is also added under this configuration. Graph is created for this use case at the end of this configuration.

Syntax:

mempool <mempool_name> size <num> buffers <num> cache <val> <cpuid>

Where:

mempool_name  : Name of the mempool used for further pool operations.
size <num>         : Size of each element in mempool
buffers <num>   : Number of elements in mempool
cache <val>        : Size of the per-core object cache
<cpuid>              : Socket id


Syntax:

ethdev <dbdf> rxq <num> txq <num> <mempool_name> promiscuous <on/off>
ethdev <dbdf> tx_offload <bitmask>
ethdev <dbdf> rx_offload <bitmask>
ethdev <dbdf> promiscuous <on/off>
ethdev <dbdf> mtu <size>

    Where:

dbdf                    : PCI id of device in DBDF format or vdev name for non-pci devices.
rxq                      : Number of Rx queues on device
txq                      : Number of Tx queues on device
mempool_name : Mempool to be attached on RQ.
rx_offload           : Supported offloads on ingress. It is bitmask of required offloads. Valid offloads are combination of RTE_ETH_RX_OFFLOAD_*
tx_offload           : Supported offloads on egress. It is bitmask of required offloads. valid offloads are combination of RTE_ETH_TX_OFFLOAD_*
promiscuous       : Toggle promiscuous mode
mtu                      : MTU size


Syntax:

neigh add ipv4 <ip> <mac>

Where:

ip    : IPv4/IPv6 address for which MAC address is to be added.
mac: MAC  address which is to be configured corresponding to given IP. 


Syntax:

ip4 addr add <ip> netmask <mask> <dbdf>

Where:

ip       : IPv4 address which is to assigned to device.
mask : Subnet mask.
dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.


Syntax:

ip6 addr add <ip> netmask <mask> <dbdf>

Where:

ip       : IPv6 address which is to assigned to device.
mask : Subnet mask.
dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.    


Node specific configuration
==================
This section consists of configuration used by nodes in graph. Based on the use case, some configurations 
can be modified on the fly. Like for l3fwd use case, route entries can be added or deleted while running
the application unlike other configuration. Following are exposed configurations:

Syntax:

route add ipv4 <node_name><ip> netmask <mask> via <gateway>

Where:

node_name : Name of node where route is to be added. Currently only supported node is ip4_lookup.
ip                 : IPv4 address which is to be added to route table.
mask           : Subnet mask.
gateway     : Gateway IP to redirect packet to next hop.


Syntax:

route add ipv6 <node_name><ip> netmask <mask> via <gateway>

Where:

node_name : Name of node where route is to be added. Currently only supported node is ip6_lookup.
ip                 : IPv6 address which is to be added to route table.
mask           : Subnet mask.
gateway     : Gateway IP to redirect packet to next hop.


Syntax:

map <node_name> port <dbdf> queue <rxq> core <core_id>

Where:

node_name : Node name which will be receiving packets as per above mapping. Currently only supported node id ethdev_rx.
rxq               : Rx queue id which is to be mapped
core_id        : Core ID to be mapped where <node_name> instance will be running.
Run use case
=========
Command under this section can be used to run the application and start to receive and transmit  packets using graph walk.

Syntax:

graph start



Note: 
    <> : Mandatory fields
    [] : Optional fields
    ;  : To add any comments, Strings added after semicolon is not used by any usecase


Example use case: l3fwd
================
;Configure usecase
graph l3fwd default 0xff

;global specific configuration
mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0
ethdev 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off
ethdev 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off 
ip4 addr add 10.0.2.1/24 0002:02:00.0
ip4 addr add 10.0.3.1/24 0002:03:00.0
neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
neigh add ipv4 10.0.2.5 62:20:DA:4F:68:70
neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70
neigh add ipv4 10.0.3.5 82:20:DA:4F:68:70

;node specific configuration
route add ipv4 ipv4_lookup 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
route add ipv4 ipv4_lookup  10.0.3.0 netmask 255.255.255.0 via 10.0.3.1


map ethdev_rx  port 0002:02:00.0 queue 0 core 6
map ethdev_rx  port 0002:03:00.0 queue 1 core 5

;Run usecase
graph start

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-07-20 15:52             ` Rakesh Kudurumalla
@ 2023-07-21  6:48               ` Jerin Jacob
  2023-07-21  7:01                 ` Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-07-21  6:48 UTC (permalink / raw)
  To: Rakesh Kudurumalla
  Cc: Vamsi Krishna Attunuru, dev, Yan, Zhirun, thomas,
	Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram, Liang,
	Cunming, Wang, Haiyue, Sunil Kumar Kori

On Thu, Jul 20, 2023 at 9:22 PM Rakesh Kudurumalla
<rkudurumalla@marvell.com> wrote:
>
>
>
> > -----Original Message-----
> > From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > Sent: Thursday, June 1, 2023 8:14 AM
> > To: Jerin Jacob <jerinjacobk@gmail.com>
> > Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>;
> > Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> > <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>; Sunil
> > Kumar Kori <skori@marvell.com>
> > Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> >

>
>
> Example use case: l3fwd
> ================
> ;Configure usecase
> graph l3fwd default 0xff
>
> ;global specific configuration
> mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0
> ethdev 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off
> ethdev 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off
> ip4 addr add 10.0.2.1/24 0002:02:00.0
> ip4 addr add 10.0.3.1/24 0002:03:00.0
> neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
> neigh add ipv4 10.0.2.5 62:20:DA:4F:68:70
> neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70
> neigh add ipv4 10.0.3.5 82:20:DA:4F:68:70
>
> ;node specific configuration
> route add ipv4 ipv4_lookup 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> route add ipv4 ipv4_lookup  10.0.3.0 netmask 255.255.255.0 via 10.0.3.1

I prefer to use node name first.

>
>
> map ethdev_rx  port 0002:02:00.0 queue 0 core 6
> map ethdev_rx  port 0002:03:00.0 queue 1 core 5

I prefer to use node name first.

Rest looks good to me.

>
> ;Run usecase
> graph start

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-07-21  6:48               ` Jerin Jacob
@ 2023-07-21  7:01                 ` Sunil Kumar Kori
  2023-07-21  7:39                   ` Rakesh Kudurumalla
  0 siblings, 1 reply; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-07-21  7:01 UTC (permalink / raw)
  To: Jerin Jacob, Rakesh Kudurumalla
  Cc: Vamsi Krishna Attunuru, dev, Yan, Zhirun, thomas,
	Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram, Liang,
	Cunming, Wang, Haiyue

> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Friday, July 21, 2023 12:18 PM
> To: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org; Yan,
> Zhirun <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob
> Kollanukkaran <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>; Liang, Cunming <cunming.liang@intel.com>;
> Wang, Haiyue <haiyue.wang@intel.com>; Sunil Kumar Kori
> <skori@marvell.com>
> Subject: Re: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> 
> On Thu, Jul 20, 2023 at 9:22 PM Rakesh Kudurumalla
> <rkudurumalla@marvell.com> wrote:
> >
> >
> >
> > > -----Original Message-----
> > > From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > > Sent: Thursday, June 1, 2023 8:14 AM
> > > To: Jerin Jacob <jerinjacobk@gmail.com>
> > > Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> > > thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>;
> > > Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> > > <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>;
> > > Sunil Kumar Kori <skori@marvell.com>
> > > Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> > >
> 
> >
> >
> > Example use case: l3fwd
> > ================
> > ;Configure usecase
> > graph l3fwd default 0xff
> >
> > ;global specific configuration
> > mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0 ethdev
> > 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off ethdev 0002:03:00.0
> > rxq 1 txq 1 mempool0 promiscuous off
> > ip4 addr add 10.0.2.1/24 0002:02:00.0
> > ip4 addr add 10.0.3.1/24 0002:03:00.0
> > neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70 neigh add ipv4 10.0.2.5
> > 62:20:DA:4F:68:70 neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70 neigh add
> > ipv4 10.0.3.5 82:20:DA:4F:68:70
> >
> > ;node specific configuration
> > route add ipv4 ipv4_lookup 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> > route add ipv4 ipv4_lookup  10.0.3.0 netmask 255.255.255.0 via
> > 10.0.3.1
> 
> I prefer to use node name first.
> 
Ack.

> >
> >
> > map ethdev_rx  port 0002:02:00.0 queue 0 core 6 map ethdev_rx  port
> > 0002:03:00.0 queue 1 core 5
> 
> I prefer to use node name first.
> 
Ack.

> Rest looks good to me.
> 
> >
> > ;Run usecase
> > graph start

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-07-21  7:01                 ` Sunil Kumar Kori
@ 2023-07-21  7:39                   ` Rakesh Kudurumalla
  2023-09-08 11:00                     ` Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Rakesh Kudurumalla @ 2023-07-21  7:39 UTC (permalink / raw)
  To: Sunil Kumar Kori, Jerin Jacob, dev
  Cc: Vamsi Krishna Attunuru, Yan, Zhirun, thomas,
	Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram, Liang,
	Cunming, Wang, Haiyue



> -----Original Message-----
> From: Sunil Kumar Kori <skori@marvell.com>
> Sent: Friday, July 21, 2023 12:31 PM
> To: Jerin Jacob <jerinjacobk@gmail.com>; Rakesh Kudurumalla
> <rkudurumalla@marvell.com>
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org; Yan,
> Zhirun <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob
> Kollanukkaran <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>; Liang, Cunming <cunming.liang@intel.com>;
> Wang, Haiyue <haiyue.wang@intel.com>
> Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> 
> > -----Original Message-----
> > From: Jerin Jacob <jerinjacobk@gmail.com>
> > Sent: Friday, July 21, 2023 12:18 PM
> > To: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> Yan,
> > Zhirun <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob
> > Kollanukkaran <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> > <ndabilpuram@marvell.com>; Liang, Cunming <cunming.liang@intel.com>;
> > Wang, Haiyue <haiyue.wang@intel.com>; Sunil Kumar Kori
> > <skori@marvell.com>
> > Subject: Re: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> >
> > On Thu, Jul 20, 2023 at 9:22 PM Rakesh Kudurumalla
> > <rkudurumalla@marvell.com> wrote:
> > >
> > >
> > >
> > > > -----Original Message-----
> > > > From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > > > Sent: Thursday, June 1, 2023 8:14 AM
> > > > To: Jerin Jacob <jerinjacobk@gmail.com>
> > > > Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> > > > thomas@monjalon.net; Jerin Jacob Kollanukkaran
> > > > <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> > > > <ndabilpuram@marvell.com>; Liang, Cunming
> > > > <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>;
> > > > Sunil Kumar Kori <skori@marvell.com>
> > > > Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph
> > > > application
> > > >
> >
> > >
> > >
> > > Example use case: l3fwd
> > > ================
> > > ;Configure usecase
> > > graph l3fwd default 0xff
> > >
> > > ;global specific configuration
> > > mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0 ethdev
> > > 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off ethdev
> > > 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off
> > > ip4 addr add 10.0.2.1/24 0002:02:00.0
> > > ip4 addr add 10.0.3.1/24 0002:03:00.0 neigh add ipv4 10.0.2.2
> > > 52:20:DA:4F:68:70 neigh add ipv4 10.0.2.5
> > > 62:20:DA:4F:68:70 neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70 neigh
> > > add
> > > ipv4 10.0.3.5 82:20:DA:4F:68:70
> > >
> > > ;node specific configuration
> > > route add ipv4 ipv4_lookup 10.0.2.0 netmask 255.255.255.0 via
> > > 10.0.2.1 route add ipv4 ipv4_lookup  10.0.3.0 netmask 255.255.255.0
> > > via
> > > 10.0.3.1
> >
> > I prefer to use node name first.
> >
> Ack.
> 
> > >
> > >
> > > map ethdev_rx  port 0002:02:00.0 queue 0 core 6 map ethdev_rx  port
> > > 0002:03:00.0 queue 1 core 5
> >
> > I prefer to use node name first.
> >
> Ack.
> 
> > Rest looks good to me.
> >
> > >
> > > ;Run usecase
> > > graph start

Addressed comments and updated specification

Graph application Interface file
Configure Use cases
=============
This section consists of which use cases are needed to be configured and model to be used along with number of coremask 
to run graph on that. Following is the exposed syntax to configure given use case.

Syntax:

graph <usecases> [usecase specific configuration] <model> [model specific configuration]

Where:

usecases: It is comma separated list defining the requested use cases. Example values are -
l3fwd
ipsec
usecase specific configuration: It defines following usecase specific configuration -
burst size (bsz)
timeout (tmo)
coremask
model: It defines the model for dequeuing packets.  Example models are -
run to completion (rtc)
multi core dispatch (mcd)
Global specific configuration
==================
This section consists of device specific configuration which are needed to make a DPDK port usable such as number

of Rx/TX queues, MTU, mempool etc. Along with it consists global network table configuration required for each use case

such as configure IP address to device, arp entries for given IP etc. Supported hardware offloads to be used by 
this use case is also added under this configuration. Graph is created for this use case at the end of this configuration.

Syntax:

mempool <mempool_name> size <num> buffers <num> cache <val> <cpuid>

Where:

mempool_name  : Name of the mempool used for further pool operations.
size <num>         : Size of each element in mempool
buffers <num>   : Number of elements in mempool
cache <val>        : Size of the per-core object cache
<cpuid>              : Socket id


Syntax:

ethdev <dbdf> rxq <num> txq <num> <mempool_name> promiscuous <on/off>
ethdev <dbdf> tx_offload <bitmask>
ethdev <dbdf> rx_offload <bitmask>
ethdev <dbdf> promiscuous <on/off>
ethdev <dbdf> mtu <size>

    Where:

dbdf                    : PCI id of device in DBDF format or vdev name for non-pci devices.
rxq                      : Number of Rx queues on device
txq                      : Number of Tx queues on device
mempool_name : Mempool to be attached on RQ.
rx_offload           : Supported offloads on ingress. It is bitmask of required offloads. Valid offloads are combination of RTE_ETH_RX_OFFLOAD_*
tx_offload           : Supported offloads on egress. It is bitmask of required offloads. valid offloads are combination of RTE_ETH_TX_OFFLOAD_*
promiscuous       : Toggle promiscuous mode
mtu                      : MTU size


Syntax:

neigh add ipv4 <ip> <mac>

Where:

ip    : IPv4/IPv6 address for which MAC address is to be added.
mac: MAC  address which is to be configured corresponding to given IP. 


Syntax:

ip4 addr add <ip> netmask <mask> <dbdf>

Where:

ip       : IPv4 address which is to assigned to device.
mask : Subnet mask.
dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.


Syntax:

ip6 addr add <ip> netmask <mask> <dbdf>

Where:

ip       : IPv6 address which is to assigned to device.
mask : Subnet mask.
dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.    


Node specific configuration
==================
This section consists of configuration used by nodes in graph. Based on the use case, some configurations 
can be modified on the fly. Like for l3fwd use case, route entries can be added or deleted while running
the application unlike other configuration. Following are exposed configurations:

Syntax:

<node_name> route add ipv4 <ip> netmask <mask> via <gateway>

Where:

node_name : Name of node where route is to be added. Currently only supported node is ip4_lookup.
ip                 : IPv4 address which is to be added to route table.
mask           : Subnet mask.
gateway     : Gateway IP to redirect packet to next hop.


Syntax:

<node_name> route add ipv6 <ip> netmask <mask> via <gateway>

Where:

node_name : Name of node where route is to be added. Currently only supported node is ip6_lookup.
ip                 : IPv6 address which is to be added to route table.
mask           : Subnet mask.
gateway     : Gateway IP to redirect packet to next hop.


Syntax:

<node_name> map port <dbdf> queue <rxq> core <core_id>

Where:

node_name : Node name which will be receiving packets as per above mapping. Currently only supported node id ethdev_rx.
rxq               : Rx queue id which is to be mapped
core_id        : Core ID to be mapped where <node_name> instance will be running.
Run use case
=========
Command under this section can be used to run the application and start to receive and transmit  packets using graph walk.

Syntax:

graph start



Note: 
    <> : Mandatory fields
    [] : Optional fields
    ;  : To add any comments, Strings added after semicolon is not used by any usecase


Example use case: l3fwd
================
;Configure usecase
graph l3fwd default 0xff

;global specific configuration
mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0
ethdev 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off
ethdev 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off 
ip4 addr add 10.0.2.1/24 0002:02:00.0
ip4 addr add 10.0.3.1/24 0002:03:00.0
neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
neigh add ipv4 10.0.2.5 62:20:DA:4F:68:70
neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70
neigh add ipv4 10.0.3.5 82:20:DA:4F:68:70

;node specific configuration
ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
ipv4_lookup route add ipv4 10.0.3.0 netmask 255.255.255.0 via 10.0.3.1


ethdev_rx  map port 0002:02:00.0 queue 0 core 6
ethdev_rx  map port 0002:03:00.0 queue 1 core 5

;Run usecase
graph start



^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v3 1/1] app/graph: add example for different usecases
  2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
  2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
  2023-05-12 11:08     ` Yan, Zhirun
@ 2023-09-08 10:49     ` skori
  2023-09-09  1:18       ` [EXT] " Nithin Kumar Dabilpuram
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
  2 siblings, 2 replies; 182+ messages in thread
From: skori @ 2023-09-08 10:49 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Current l3fwd-graph application only validates l3fwd use case.
To scale up this, new application will be added with a framework
to run as user's provided usecases.

Required configuration and use cases details are fetched via a
static .cli file which will be used to create a graph for
requested uscases.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 208 ++++++
 app/graph/cli.h                              |  48 ++
 app/graph/cli_priv.h                         |  19 +
 app/graph/conn.c                             | 284 +++++++++
 app/graph/conn.h                             |  46 ++
 app/graph/ethdev.c                           | 632 +++++++++++++++++++
 app/graph/ethdev.h                           |  28 +
 app/graph/ethdev_priv.h                      |  46 ++
 app/graph/ethdev_rx.c                        | 139 ++++
 app/graph/ethdev_rx.h                        |  32 +
 app/graph/ethdev_rx_priv.h                   |  23 +
 app/graph/examples/l3fwd.cli                 |  87 +++
 app/graph/graph.c                            | 383 +++++++++++
 app/graph/graph.h                            |  11 +
 app/graph/graph_priv.h                       |  32 +
 app/graph/ip4_route.c                        | 146 +++++
 app/graph/ip6_route.c                        | 154 +++++
 app/graph/l3fwd.c                            | 152 +++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 201 ++++++
 app/graph/mempool.c                          | 134 ++++
 app/graph/mempool.h                          |  18 +
 app/graph/mempool_priv.h                     |  16 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  33 +
 app/graph/neigh.c                            | 269 ++++++++
 app/graph/neigh.h                            |  11 +
 app/graph/neigh_priv.h                       |  22 +
 app/graph/route.h                            |  30 +
 app/graph/utils.c                            | 155 +++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/tools/graph.rst                   | 171 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++
 doc/guides/tools/index.rst                   |   1 +
 36 files changed, 3799 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/cli_priv.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/MAINTAINERS b/MAINTAINERS
index 698608cdb2..7f149bd060 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1806,6 +1806,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..237fa8008f
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,208 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "cli_priv.h"
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+static struct cli_node_head module_list = STAILQ_HEAD_INITIALIZER(module_list);
+
+#define PARSE_DELIMITER " \f\n\r\t\v"
+
+static int
+tokenize_string_parse(char *string, char *tokens[], uint32_t *n_tokens)
+{
+	uint32_t i;
+
+	if ((string == NULL) ||
+		(tokens == NULL) ||
+		(*n_tokens < 1))
+		return -EINVAL;
+
+	for (i = 0; i < *n_tokens; i++) {
+		tokens[i] = strtok_r(string, PARSE_DELIMITER, &string);
+		if (tokens[i] == NULL)
+			break;
+	}
+
+	if ((i == *n_tokens) && strtok_r(string, PARSE_DELIMITER, &string))
+		return -E2BIG;
+
+	*n_tokens = i;
+	return 0;
+}
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+static bool
+module_list_has_cmd_registered(const char *cmd)
+{
+	struct cli_node *node;
+
+	STAILQ_FOREACH(node, &module_list, next) {
+		if (strcmp(node->cmd, cmd) == 0) {
+			rte_errno = EEXIST;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void
+cli_module_register(const struct cli_module *module)
+{
+	struct cli_node *node;
+
+	/* Check sanity */
+	if (module == NULL || module->process == NULL) {
+		rte_errno = EINVAL;
+		return;
+	}
+
+	/* Check for duplicate name */
+	if (module_list_has_cmd_registered(module->cmd)) {
+		printf("module %s is already registered\n", module->cmd);
+		return;
+	}
+
+	node = malloc(sizeof(struct cli_node));
+	if (node == NULL) {
+		rte_errno = ENOMEM;
+		return;
+	}
+
+	/* Initialize the node */
+	if (rte_strscpy(node->cmd, module->cmd, APP_CLI_CMD_NAME_SIZE) < 0) {
+		free(node);
+		return;
+	}
+	node->process = module->process;
+	node->usage = module->usage;
+
+	/* Add the node at tail */
+	STAILQ_INSERT_TAIL(&module_list, node, next);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, void *obj)
+{
+	char *tokens[CMD_MAX_TOKENS];
+	struct cli_node *node;
+	uint32_t n_tokens;
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	n_tokens = RTE_DIM(tokens);
+	rc = tokenize_string_parse(in, tokens, &n_tokens);
+	if (rc) {
+		snprintf(out, out_size, MSG_ARG_TOO_MANY, "");
+		return;
+	}
+
+	if (n_tokens == 0)
+		return;
+
+	if ((n_tokens == 1) && strcmp(tokens[0], "help") == 0) {
+		STAILQ_FOREACH(node, &module_list, next) {
+			node->usage(tokens, n_tokens, out, out_size, obj);
+		}
+		return;
+	}
+
+	if ((n_tokens >= 2) && strcmp(tokens[0], "help") == 0) {
+		STAILQ_FOREACH(node, &module_list, next) {
+			if (strcmp(node->cmd, tokens[1]) == 0) {
+				node->usage(tokens, n_tokens, out, out_size, obj);
+				return;
+			}
+		}
+		snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
+		return;
+	}
+
+	STAILQ_FOREACH(node, &module_list, next) {
+		if (strcmp(node->cmd, tokens[0]) == 0) {
+			rc = node->process(tokens, n_tokens, out, out_size, obj);
+			if (rc < 0)
+				snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+			return;
+		}
+	}
+
+	snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (1) {
+		if (fgets(msg_in, msg_in_len_max + 1, f) == NULL)
+			break;
+
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..2bd89f3d1f
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+/* Typedefs */
+typedef int (*cli_module_t)(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
+			     void *obj);
+
+/* Structures */
+struct cli_module {
+	char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command to be registered. */
+	cli_module_t process; /**< Command process function. */
+	cli_module_t usage; /**< Help command process function. */
+};
+
+/* APIs */
+void cli_module_register(const struct cli_module *module);
+
+#define CLI_REGISTER(module)			\
+	RTE_INIT(cli_register_##module)		\
+	{					\
+		cli_module_register(&module);	\
+	}
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/cli_priv.h b/app/graph/cli_priv.h
new file mode 100644
index 0000000000..9ecc89c353
--- /dev/null
+++ b/app/graph/cli_priv.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_PRIV_H
+#define APP_GRAPH_CLI_PRIV_H
+
+#include "cli.h"
+
+struct cli_node {
+	STAILQ_ENTRY(cli_node) next;	 /**< Next node in the list. */
+	char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command. */
+	cli_module_t process;		 /**< Command process function. */
+	cli_module_t usage;		/**< Help command process function. */
+};
+
+STAILQ_HEAD(cli_node_head, cli_node);
+
+#endif
diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..dabc8deca2
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	rc = listen(fd_server, 16);
+	if (rc == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..840a8ca42f
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,632 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name> "
+		    "[mtu <mtu_sz>]";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+struct ethdev port_list[RTE_MAX_ETHPORTS];
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	return &port_list[portid].config.rx.mp;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip)
+{
+	int portid = -EINVAL;
+	int i;
+
+	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
+		if ((port_list[i].ip4_addr.ip & route4[i].netmask) == (ip & route4[i].netmask))
+			break;
+	}
+
+	if (i == RTE_MAX_ETHPORTS)
+		return portid;
+
+	return port_list[i].config.port_id;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip)
+{
+	int portid = -EINVAL;
+	int i, j;
+
+	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if ((port_list[i].ip6_addr.ip[j] & route6[i].mask[j]) !=
+			    (ip[j] & route6[i].mask[j]))
+				break;
+		}
+
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			break;
+	}
+
+	if (i == RTE_MAX_ETHPORTS)
+		return portid;
+
+	return port_list[i].config.port_id;
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	/* clean up the EAL */
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name, char **out, size_t *out_size)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	snprintf(*out, *out_size,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(*out);
+	*out_size -= length;
+	*out += length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	port_list[portid].ip4_addr.ip = config->ip;
+	port_list[portid].ip4_addr.mask = config->mask;
+	return 0;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		port_list[portid].ip6_addr.ip[i] = config->ip[i];
+		port_list[portid].ip6_addr.mask[i] = config->mask[i];
+	}
+
+	return 0;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	if (enable)
+		rc = rte_eth_promiscuous_enable(portid);
+	else
+		rc = rte_eth_promiscuous_disable(portid);
+
+	if (rc < 0)
+		return rc;
+
+	port_list[portid].config.promiscuous = enable;
+	return 0;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_dev_set_mtu(portid, mtu);
+	if (rc < 0)
+		return rc;
+
+	port_list[portid].config.mtu = mtu;
+	return 0;
+}
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct rte_ether_addr smac;
+	int numa_node, rc;
+	uint16_t port_id = 0;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc)
+		return -EINVAL;
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool)
+		return -EINVAL;
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512)
+			return -EINVAL;
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX)
+			return -EINVAL;
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues)
+				return -EINVAL;
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0)
+		return -EINVAL;
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0)
+		return -EINVAL;
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0)
+			return -EINVAL;
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0)
+			return -EINVAL;
+	}
+
+	memcpy(&port_list[port_id].config, params, sizeof(struct ethdev_config));
+	memcpy(port_list[port_id].config.dev_name, name, strlen(name));
+	port_list[port_id].config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+	return 0;
+}
+
+static int
+cmd_ethdev_mtu(char **tokens, uint32_t n_tokens __rte_unused, char *out, size_t out_size,
+	       void *obj __rte_unused)
+{
+	int rc = -EINVAL;
+	uint32_t mtu = 0;
+
+	if (n_tokens != 4) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return rc;
+	}
+
+	if (parser_uint32_read(&mtu, tokens[3]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
+		return rc;
+	}
+
+	rc = ethdev_mtu_config(tokens[1], mtu);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+	return rc;
+}
+
+static int
+cmd_ethdev_prom_mode(char **tokens, uint32_t n_tokens __rte_unused, char *out, size_t out_size,
+		     void *obj __rte_unused)
+{
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (n_tokens != 4) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return rc;
+	}
+
+	if (strcmp(tokens[3], "on") == 0)
+		enable = true;
+
+	rc = ethdev_prom_mode_config(tokens[1], enable);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+	return rc;
+}
+
+static int
+cmd_ip4_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (strcmp(tokens[3], "addr")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
+		goto exit;
+	}
+
+	if (strcmp(tokens[4], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
+		goto exit;
+	}
+
+	if (parser_ip4_read(&config.ip, tokens[5])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
+		goto exit;
+	}
+
+	if (strcmp(tokens[6], "netmask")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
+		goto exit;
+	}
+
+	if (parser_ip4_read(&config.mask, tokens[7])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
+		goto exit;
+	}
+
+	rc = ethdev_ip4_addr_add(tokens[1], &config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static int
+cmd_ip6_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (strcmp(tokens[3], "addr")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
+		goto exit;
+	}
+
+	if (strcmp(tokens[4], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
+		goto exit;
+	}
+
+	if (parser_ip6_read(config.ip, tokens[5])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
+		goto exit;
+	}
+
+	if (strcmp(tokens[6], "netmask")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
+		goto exit;
+	}
+
+	if (parser_ip6_read(config.mask, tokens[7])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
+		goto exit;
+	}
+
+	rc = ethdev_ip6_addr_add(tokens[1], &config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static int
+cmd_ethdev_show(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
+	void *obj __rte_unused)
+{
+	int rc = -EINVAL;
+
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return rc;
+	}
+
+	rc = ethdev_show(tokens[1], &out, &out_size);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
+
+	return rc;
+}
+
+static int
+cmd_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	struct ethdev_config config;
+	char *name;
+	int rc;
+
+	if (n_tokens < 7) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return -EINVAL;
+	}
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	name = tokens[1];
+
+	if (strcmp(tokens[2], "rxq") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
+		return -EINVAL;
+	}
+
+	if (parser_uint32_read(&config.rx.n_queues, tokens[3]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
+		return -EINVAL;
+	}
+
+	if (strcmp(tokens[4], "txq") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "txq");
+		return -EINVAL;
+	}
+
+	if (parser_uint32_read(&config.tx.n_queues, tokens[5]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
+		return -EINVAL;
+	}
+
+	mempcpy(config.rx.mempool_name, tokens[6], strlen(tokens[6]));
+
+	if (n_tokens > 7) {
+		if (strcmp(tokens[7], "mtu") != 0) {
+			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "mtu");
+			return -EINVAL;
+		}
+
+		if (parser_uint32_read(&config.mtu, tokens[8]) != 0) {
+			snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
+			return -EINVAL;
+		}
+	}
+
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+
+	rc = ethdev_process(name, &config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+	return rc;
+}
+
+static int
+cli_ethdev_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		   size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip4_addr_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip6_addr_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_prom_mode_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_mtu_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_show_help);
+
+	return 0;
+}
+
+static int
+cli_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
+{
+	if (strcmp(tokens[2], "show") == 0)
+		return cmd_ethdev_show(tokens, n_tokens, out, out_size, obj);
+	else if (strcmp(tokens[2], "mtu") == 0)
+		return cmd_ethdev_mtu(tokens, n_tokens, out, out_size, obj);
+	else if (strcmp(tokens[2], "promiscuous") == 0)
+		return cmd_ethdev_prom_mode(tokens, n_tokens, out, out_size, obj);
+	else if (strcmp(tokens[2], "ip4") == 0)
+		return cmd_ip4_addr(tokens, n_tokens, out, out_size, obj);
+	else if (strcmp(tokens[2], "ip6") == 0)
+		return cmd_ip6_addr(tokens, n_tokens, out, out_size, obj);
+	else
+		return cmd_ethdev(tokens, n_tokens, out, out_size, obj);
+}
+
+static struct cli_module ethdev = {
+	.cmd = "ethdev",
+	.process = cli_ethdev,
+	.usage = cli_ethdev_help,
+};
+
+CLI_REGISTER(ethdev);
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..9c3de49826
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip);
+int16_t ethdev_portid_by_ip6(uint8_t *ip);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..1026c2e5b6
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+
+#endif
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..d706d145c1
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static int
+cli_ethdev_rx_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		   size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "---------------------------- ethdev_rx command help ---------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ethdev_rx_help);
+	return 0;
+}
+
+static int
+cli_ethdev_rx(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	char name[RTE_ETH_NAME_MAX_LEN];
+	uint32_t core_id, queue;
+	int rc = -EINVAL;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	strcpy(name, tokens[3]);
+
+	if (strcmp(tokens[4], "queue") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&queue, tokens[5]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "queue");
+		goto exit;
+	}
+
+	if (strcmp(tokens[6], "core") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "core_id");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&core_id, tokens[7]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "queue");
+		goto exit;
+	}
+
+	rc = ethdev_rx_map_add(name, queue, core_id);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static struct cli_module ethdev_rx = {
+	.cmd = "ethdev_rx",
+	.process = cli_ethdev_rx,
+	.usage = cli_ethdev_rx_help,
+};
+
+CLI_REGISTER(ethdev_rx);
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..d2c18f545f
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..d714f83739
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..9986e1b73e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask ff model default
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0 mtu 1500
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0 mtu 1600
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0 mtu 1500
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0 mtu 1600
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..8c75574ecd
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,383 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strcpy(graph_config.usecases[j].name, token);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static int
+graph_start(void)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+
+	return 0;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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
+graph_config_process(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
+		     void *obj __rte_unused)
+{
+	uint32_t bsz = 32, tmo = 0, coremask = 0xf;
+	struct graph_config config;
+	int idx = 2, rc;
+	uint8_t model;
+
+	if (n_tokens < 4) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+next_arg:
+	if (strcmp(tokens[idx], "model")) {
+		if (strcmp(tokens[idx], "bsz") == 0) {
+			if (parser_uint32_read(&bsz, tokens[idx + 1])) {
+				snprintf(out, out_size, MSG_ARG_INVALID, "bsz");
+				return;
+			}
+
+		} else if (strcmp(tokens[idx], "tmo") == 0) {
+			if (parser_uint32_read(&tmo, tokens[idx + 1])) {
+				snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
+				return;
+			}
+		} else if (strcmp(tokens[idx], "coremask") == 0) {
+			coremask = strtol(tokens[idx + 1], NULL, 16);
+			if (coremask == 0) {
+				snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
+				return;
+			}
+		} else {
+			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "usecases params");
+			return;
+		}
+
+		idx += 2;
+		goto next_arg;
+	} else {
+		if (strcmp(tokens[idx + 1], "default") == 0) {
+			model = GRAPH_MODEL_RTC;
+		} else if (strcmp(tokens[idx + 1], "rtc") == 0) {
+			model = GRAPH_MODEL_RTC;
+		} else if (strcmp(tokens[idx + 1], "mcd") == 0) {
+			model = GRAPH_MODEL_MCD;
+		} else {
+			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "model arguments");
+			return;
+		}
+	}
+
+	config.params.bsz = bsz;
+	config.params.tmo = tmo;
+	config.params.coremask = coremask;
+	config.model = model;
+	rc = graph_config_add(tokens[1], &config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+}
+
+static int
+cli_graph_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+	       size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "----------------------------- graph command help -----------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_graph_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", "graph start");
+	return 0;
+}
+
+static int
+cli_graph(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	if (strcmp(tokens[1], "start") == 0)
+		graph_start();
+	else
+		graph_config_process(tokens, n_tokens, out, out_size, obj);
+
+	return 0;
+}
+
+static struct cli_module graph = {
+	.cmd = "graph",
+	.process = cli_graph,
+	.usage = cli_graph_help,
+};
+
+CLI_REGISTER(graph);
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..126e967d75
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..655a028fb2
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..5aba5b38f2
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route_ip4_add(struct ipv4_route_config *route)
+{
+	int i;
+
+	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
+		if (!route4[i].is_used)
+			break;
+	}
+
+	if (i == MAX_ROUTE_ENTRIES)
+		return -ENOMEM;
+
+	route4[i].ip = route->ip;
+	route4[i].netmask = route->netmask;
+	route4[i].via = route->via;
+	route4[i].is_used = true;
+	return 0;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct ipv4_route_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid, i;
+
+	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
+		if (route4[i].is_used)
+			route = &route4[i];
+
+		portid = ethdev_portid_by_ip4(route->via);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+
+		depth = convert_netmask_to_depth(route->netmask);
+
+		rc = rte_node_ip4_route_add(route->ip, depth, portid,
+					     RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int
+cli_ipv4_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		     size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "--------------------------- ipv4_lookup command help --------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ipv4_lookup_help);
+	return 0;
+}
+
+static int
+cli_ipv4_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
+		void *obj __rte_unused)
+{
+	struct ipv4_route_config config;
+	int rc = -EINVAL;
+
+	if (n_tokens != 9) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (parser_ip4_read(&config.ip, tokens[4])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ipv4");
+		goto exit;
+	}
+
+	if (strcmp(tokens[5], "netmask")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
+		goto exit;
+	}
+
+	if (parser_ip4_read(&config.netmask, tokens[6])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
+		goto exit;
+	}
+
+	if (strcmp(tokens[7], "via")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
+		goto exit;
+	}
+
+	if (parser_ip4_read(&config.via, tokens[8])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "via ip");
+		goto exit;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static struct cli_module ipv4_lookup = {
+	.cmd = "ipv4_lookup",
+	.process = cli_ipv4_lookup,
+	.usage = cli_ipv4_lookup_help,
+};
+
+CLI_REGISTER(ipv4_lookup);
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..2c5397f9d3
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route_ip6_add(struct ipv6_route_config *route)
+{
+	int i, j;
+
+	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
+		if (!route6[i].is_used)
+			break;
+	}
+
+	if (i == RTE_MAX_ETHPORTS)
+		return -ENOMEM;
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		route6[i].ip[j] = route->ip[j];
+		route6[i].mask[j] = route->mask[j];
+		route6[i].gateway[j] = route->gateway[j];
+	}
+	route6[i].is_used = true;
+
+	return 0;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct ipv6_route_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid, i;
+
+	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
+		if (route6[i].is_used)
+			route = &route6[i];
+
+		portid = ethdev_portid_by_ip6(route->gateway);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+
+		depth = convert_ip6_netmask_to_depth(route->mask);
+
+		rc = rte_node_ip6_route_add(route->ip, depth, portid,
+					     RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int
+cli_ipv6_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		     size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "--------------------------- ipv6_lookup command help --------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_ipv6_lookup_help);
+	return 0;
+}
+
+static int
+cli_ipv6_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
+		void *obj __rte_unused)
+{
+	struct ipv6_route_config config;
+	int rc = -EINVAL;
+
+	if (n_tokens != 9) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (parser_ip6_read(config.ip, tokens[4])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ipv6");
+		goto exit;
+	}
+
+	if (strcmp(tokens[5], "netmask")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
+		goto exit;
+	}
+
+	if (parser_ip6_read(config.mask, tokens[6])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
+		goto exit;
+	}
+
+	if (strcmp(tokens[7], "via")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
+		goto exit;
+	}
+
+	if (parser_ip6_read(config.gateway, tokens[8])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "gateway ip");
+		goto exit;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static struct cli_module ipv6_lookup = {
+	.cmd = "ipv6_lookup",
+	.process = cli_ipv6_lookup,
+	.usage = cli_ipv6_lookup_help,
+};
+
+CLI_REGISTER(ipv6_lookup);
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..85b8b2618e
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
+static uint64_t packet_to_capture;
+static int pcap_trace_enable;
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t lcore_id;
+	int rc;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_conf.pcap_enable = pcap_trace_enable;
+	graph_conf.num_pkt_to_capture = packet_to_capture;
+	graph_conf.pcap_filename = pcap_filename;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	/* Wait for worker cores to exit */
+	rc = 0;
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		rc = rte_eal_wait_lcore(lcore_id);
+		/* Destroy graph */
+		if (rc < 0 ||
+		    rte_graph_destroy(rte_graph_from_name(lcore_conf[lcore_id].name))) {
+			rc = -1;
+			break;
+		}
+	}
+	free(node_patterns);
+
+	ethdev_stop();
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..e9934025bf
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
+			    "[--help]\n";
+
+static struct app_params {
+	struct conn_params conn;
+	char *script_name;
+	bool enable_graph_stats;
+} app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
+	.script_name = NULL,
+	.enable_graph_stats = false,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
+	};
+	int h_present, p_present, s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	h_present = 0;
+	p_present = 0;
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t) atoi(optarg);
+			break;
+
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'g':
+			app.enable_graph_stats = true;
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
+int
+main(int argc, char **argv)
+{
+	struct conn *conn;
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	/* Dispatch loop */
+	while (1) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+	}
+
+	/* clean up the EAL */
+	rte_eal_cleanup();
+}
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..1cee66abed
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static int
+cli_mempool_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		 size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "---------------------------- mempool command help ----------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_mempool_help);
+	return 0;
+}
+
+static int
+cli_mempool(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	uint32_t pkt_buffer_size, pool_size, cache_size, numa_node;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+	if (n_tokens != 10) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (strcmp(tokens[2], "size")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "size");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&pkt_buffer_size, tokens[3])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "mbuf_size");
+		goto exit;
+	}
+
+	if (strcmp(tokens[4], "buffers")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "buffers");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&pool_size, tokens[5])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "number_of_buffers");
+		goto exit;
+	}
+
+	if (strcmp(tokens[6], "cache")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "cache");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&cache_size, tokens[7])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "cache_size");
+		goto exit;
+	}
+
+	if (strcmp(tokens[8], "numa")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "numa");
+		goto exit;
+	}
+
+	if (parser_uint32_read(&numa_node, tokens[9])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "numa_id");
+		goto exit;
+	}
+
+	strcpy(config.name, tokens[1]);
+	config.name[strlen(tokens[1])] = '\0';
+	config.pool_size = pool_size;
+	config.buffer_size = pkt_buffer_size;
+	config.cache_size = cache_size;
+	config.numa_node = numa_node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static struct cli_module mempool = {
+	.cmd = "mempool",
+	.process = cli_mempool,
+	.usage = cli_mempool_help,
+};
+
+CLI_REGISTER(mempool);
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..5fc788199d
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..5a55722b32
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..a3011e504b
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node']
+sources = files(
+        'cli.c',
+        'conn.c',
+        'ethdev_rx.c',
+        'ethdev.c',
+        'graph.c',
+        'ip4_route.c',
+        'ip6_route.c',
+        'main.c',
+        'mempool.c',
+        'neigh.c',
+        'l3fwd.c',
+        'utils.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..09b10bc672
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#include "conn.h"
+#include "cli.h"
+#include "ethdev.h"
+#include "ethdev_rx.h"
+#include "graph.h"
+#include "l3fwd.h"
+#include "mempool.h"
+#include "neigh.h"
+#include "route.h"
+#include "utils.h"
+
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+bool app_graph_stats_enabled(void);
+
+#endif
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..07766758c9
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct ipv4_neigh_config neigh4[MAX_NEIGH_ENTRIES];
+struct ipv6_neigh_config neigh6[MAX_NEIGH_ENTRIES];
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	int i;
+
+	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
+		if (!neigh4[i].is_used)
+			break;
+	}
+
+	if (i == MAX_NEIGH_ENTRIES)
+		return -ENOMEM;
+
+	neigh4[i].ip = ip;
+	neigh4[i].mac = mac;
+	neigh4[i].is_used = true;
+	return 0;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	int i, j;
+
+	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
+		if (!neigh6[i].is_used)
+			break;
+	}
+
+	if (i == MAX_NEIGH_ENTRIES)
+		return -ENOMEM;
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		neigh6[i].ip[j] = ip[j];
+
+	neigh6[i].mac = mac;
+	neigh6[i].is_used = true;
+	return 0;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct ipv4_neigh_config *neigh;
+	int16_t portid = 0;
+	int rc, i;
+
+	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
+		if (!neigh4[i].is_used)
+			continue;
+
+		neigh = &neigh4[i];
+		portid = ethdev_portid_by_ip4(neigh->ip);
+		if (portid < 0) {
+			printf("Invalid portid found to add  neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct ipv6_neigh_config *neigh;
+	int16_t portid = 0;
+	int rc, i;
+
+	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
+		if (!neigh6[i].is_used)
+			continue;
+
+		neigh = &neigh6[i];
+		portid = ethdev_portid_by_ip6(neigh->ip);
+		if (portid < 0) {
+			printf("Invalid portid found to add neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n",
+				rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int
+cmd_neigh_v4(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (strcmp(tokens[1], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
+		goto exit;
+	}
+
+	if (strcmp(tokens[2], "ipv4")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv4");
+		goto exit;
+	}
+
+	if (parser_ip4_read(&ip, tokens[3])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
+		goto exit;
+	}
+
+	if (parser_mac_read(&mac, tokens[4])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "mac");
+		goto exit;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static int
+cmd_neigh_v6(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj __rte_unused)
+{
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		goto exit;
+	}
+
+	if (strcmp(tokens[1], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
+		goto exit;
+	}
+
+	if (strcmp(tokens[2], "ipv6")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv6");
+		goto exit;
+	}
+
+	if (parser_ip6_read(ip, tokens[3])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
+		goto exit;
+	}
+
+	if (parser_mac_read(&mac, tokens[4])) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "mac");
+		goto exit;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+
+exit:
+	return rc;
+}
+
+static int
+cli_neigh_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
+		     size_t out_size, void *obj __rte_unused)
+{
+	size_t len;
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "\n%s\n",
+		 "----------------------------- neigh command help -----------------------------");
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_neigh_v4_help);
+
+	len = strlen(out);
+	snprintf(out + len, out_size, "%s\n", cmd_neigh_v6_help);
+	return 0;
+}
+
+static int
+cli_neigh(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
+{
+	if (strcmp(tokens[2], "ipv4") == 0)
+		return cmd_neigh_v4(tokens, n_tokens, out, out_size, obj);
+	else
+		return cmd_neigh_v6(tokens, n_tokens, out, out_size, obj);
+}
+
+static struct cli_module neigh = {
+	.cmd = "neigh",
+	.process = cli_neigh,
+	.usage = cli_neigh_help,
+};
+
+CLI_REGISTER(neigh);
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..3964c37bb0
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..745dc7d671
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct ipv4_neigh_config {
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+struct ipv6_neigh_config {
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+#endif
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..6b4acf3344
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ipv4_route_config {
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+struct ipv6_route_config {
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+extern struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
+extern struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
+
+int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
+
+#endif
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..48a83e738c
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (atoi(token) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..c90dc9ad7f
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,171 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ * l3fwd
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :header-rows: 1
+   :widths: auto
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+     - Command to express the desired use case
+     - No
+     - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name> [mtu <mtu_sz>]
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - No
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - No
+     - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - No
+     - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - No
+     - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - No
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - No
+     - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address 0.0.0.0 and port number 8086 if
+``-h`` and ``-p`` is not given otherwise user provided IPv4 address and port number will
+be used.
+
+After successful launch of application, client can connect to using host/port address and
+console will be accessed with prompt ``graph>``.
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
  2023-07-21  7:39                   ` Rakesh Kudurumalla
@ 2023-09-08 11:00                     ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-09-08 11:00 UTC (permalink / raw)
  To: Rakesh Kudurumalla, Jerin Jacob, dev
  Cc: Vamsi Krishna Attunuru, Yan, Zhirun, thomas,
	Jerin Jacob Kollanukkaran, Nithin Kumar Dabilpuram, Liang,
	Cunming, Wang, Haiyue

Hello everyone,

As per proposed CLIs to configure graph for different use cases, v3 is implemented under the following request:
https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/
Please take look on and provide feedback.

Regards
Sunil Kumar Kori

> -----Original Message-----
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Sent: Friday, July 21, 2023 1:09 PM
> To: Sunil Kumar Kori <skori@marvell.com>; Jerin Jacob
> <jerinjacobk@gmail.com>; dev@dpdk.org
> Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; Yan, Zhirun
> <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob Kollanukkaran
> <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>; Liang, Cunming <cunming.liang@intel.com>;
> Wang, Haiyue <haiyue.wang@intel.com>
> Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> 
> 
> 
> > -----Original Message-----
> > From: Sunil Kumar Kori <skori@marvell.com>
> > Sent: Friday, July 21, 2023 12:31 PM
> > To: Jerin Jacob <jerinjacobk@gmail.com>; Rakesh Kudurumalla
> > <rkudurumalla@marvell.com>
> > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> Yan,
> > Zhirun <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob
> > Kollanukkaran <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> > <ndabilpuram@marvell.com>; Liang, Cunming <cunming.liang@intel.com>;
> > Wang, Haiyue <haiyue.wang@intel.com>
> > Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> >
> > > -----Original Message-----
> > > From: Jerin Jacob <jerinjacobk@gmail.com>
> > > Sent: Friday, July 21, 2023 12:18 PM
> > > To: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > > Cc: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> > Yan,
> > > Zhirun <zhirun.yan@intel.com>; thomas@monjalon.net; Jerin Jacob
> > > Kollanukkaran <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> > > <ndabilpuram@marvell.com>; Liang, Cunming
> <cunming.liang@intel.com>;
> > > Wang, Haiyue <haiyue.wang@intel.com>; Sunil Kumar Kori
> > > <skori@marvell.com>
> > > Subject: Re: [EXT] Re: [PATCH v2 4/4] app: add testgraph application
> > >
> > > On Thu, Jul 20, 2023 at 9:22 PM Rakesh Kudurumalla
> > > <rkudurumalla@marvell.com> wrote:
> > > >
> > > >
> > > >
> > > > > -----Original Message-----
> > > > > From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
> > > > > Sent: Thursday, June 1, 2023 8:14 AM
> > > > > To: Jerin Jacob <jerinjacobk@gmail.com>
> > > > > Cc: Yan, Zhirun <zhirun.yan@intel.com>; dev@dpdk.org;
> > > > > thomas@monjalon.net; Jerin Jacob Kollanukkaran
> > > > > <jerinj@marvell.com>; Nithin Kumar Dabilpuram
> > > > > <ndabilpuram@marvell.com>; Liang, Cunming
> > > > > <cunming.liang@intel.com>; Wang, Haiyue
> <haiyue.wang@intel.com>;
> > > > > Sunil Kumar Kori <skori@marvell.com>
> > > > > Subject: RE: [EXT] Re: [PATCH v2 4/4] app: add testgraph
> > > > > application
> > > > >
> > >
> > > >
> > > >
> > > > Example use case: l3fwd
> > > > ================
> > > > ;Configure usecase
> > > > graph l3fwd default 0xff
> > > >
> > > > ;global specific configuration
> > > > mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0 ethdev
> > > > 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off ethdev
> > > > 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off
> > > > ip4 addr add 10.0.2.1/24 0002:02:00.0
> > > > ip4 addr add 10.0.3.1/24 0002:03:00.0 neigh add ipv4 10.0.2.2
> > > > 52:20:DA:4F:68:70 neigh add ipv4 10.0.2.5
> > > > 62:20:DA:4F:68:70 neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70 neigh
> > > > add
> > > > ipv4 10.0.3.5 82:20:DA:4F:68:70
> > > >
> > > > ;node specific configuration
> > > > route add ipv4 ipv4_lookup 10.0.2.0 netmask 255.255.255.0 via
> > > > 10.0.2.1 route add ipv4 ipv4_lookup  10.0.3.0 netmask 255.255.255.0
> > > > via
> > > > 10.0.3.1
> > >
> > > I prefer to use node name first.
> > >
> > Ack.
> >
> > > >
> > > >
> > > > map ethdev_rx  port 0002:02:00.0 queue 0 core 6 map ethdev_rx  port
> > > > 0002:03:00.0 queue 1 core 5
> > >
> > > I prefer to use node name first.
> > >
> > Ack.
> >
> > > Rest looks good to me.
> > >
> > > >
> > > > ;Run usecase
> > > > graph start
> 
> Addressed comments and updated specification
> 
> Graph application Interface file
> Configure Use cases
> =============
> This section consists of which use cases are needed to be configured and
> model to be used along with number of coremask
> to run graph on that. Following is the exposed syntax to configure given use
> case.
> 
> Syntax:
> 
> graph <usecases> [usecase specific configuration] <model> [model specific
> configuration]
> 
> Where:
> 
> usecases: It is comma separated list defining the requested use cases.
> Example values are -
> l3fwd
> ipsec
> usecase specific configuration: It defines following usecase specific
> configuration -
> burst size (bsz)
> timeout (tmo)
> coremask
> model: It defines the model for dequeuing packets.  Example models are -
> run to completion (rtc)
> multi core dispatch (mcd)
> Global specific configuration
> ==================
> This section consists of device specific configuration which are needed to
> make a DPDK port usable such as number
> 
> of Rx/TX queues, MTU, mempool etc. Along with it consists global network
> table configuration required for each use case
> 
> such as configure IP address to device, arp entries for given IP etc. Supported
> hardware offloads to be used by
> this use case is also added under this configuration. Graph is created for this
> use case at the end of this configuration.
> 
> Syntax:
> 
> mempool <mempool_name> size <num> buffers <num> cache <val> <cpuid>
> 
> Where:
> 
> mempool_name  : Name of the mempool used for further pool operations.
> size <num>         : Size of each element in mempool
> buffers <num>   : Number of elements in mempool
> cache <val>        : Size of the per-core object cache
> <cpuid>              : Socket id
> 
> 
> Syntax:
> 
> ethdev <dbdf> rxq <num> txq <num> <mempool_name> promiscuous
> <on/off>
> ethdev <dbdf> tx_offload <bitmask>
> ethdev <dbdf> rx_offload <bitmask>
> ethdev <dbdf> promiscuous <on/off>
> ethdev <dbdf> mtu <size>
> 
>     Where:
> 
> dbdf                    : PCI id of device in DBDF format or vdev name for non-pci
> devices.
> rxq                      : Number of Rx queues on device
> txq                      : Number of Tx queues on device
> mempool_name : Mempool to be attached on RQ.
> rx_offload           : Supported offloads on ingress. It is bitmask of required
> offloads. Valid offloads are combination of RTE_ETH_RX_OFFLOAD_*
> tx_offload           : Supported offloads on egress. It is bitmask of required
> offloads. valid offloads are combination of RTE_ETH_TX_OFFLOAD_*
> promiscuous       : Toggle promiscuous mode
> mtu                      : MTU size
> 
> 
> Syntax:
> 
> neigh add ipv4 <ip> <mac>
> 
> Where:
> 
> ip    : IPv4/IPv6 address for which MAC address is to be added.
> mac: MAC  address which is to be configured corresponding to given IP.
> 
> 
> Syntax:
> 
> ip4 addr add <ip> netmask <mask> <dbdf>
> 
> Where:
> 
> ip       : IPv4 address which is to assigned to device.
> mask : Subnet mask.
> dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.
> 
> 
> Syntax:
> 
> ip6 addr add <ip> netmask <mask> <dbdf>
> 
> Where:
> 
> ip       : IPv6 address which is to assigned to device.
> mask : Subnet mask.
> dbdf  : PCI id of device in DBDF format or vdev name for non-pci devices.
> 
> 
> Node specific configuration
> ==================
> This section consists of configuration used by nodes in graph. Based on the
> use case, some configurations
> can be modified on the fly. Like for l3fwd use case, route entries can be
> added or deleted while running
> the application unlike other configuration. Following are exposed
> configurations:
> 
> Syntax:
> 
> <node_name> route add ipv4 <ip> netmask <mask> via <gateway>
> 
> Where:
> 
> node_name : Name of node where route is to be added. Currently only
> supported node is ip4_lookup.
> ip                 : IPv4 address which is to be added to route table.
> mask           : Subnet mask.
> gateway     : Gateway IP to redirect packet to next hop.
> 
> 
> Syntax:
> 
> <node_name> route add ipv6 <ip> netmask <mask> via <gateway>
> 
> Where:
> 
> node_name : Name of node where route is to be added. Currently only
> supported node is ip6_lookup.
> ip                 : IPv6 address which is to be added to route table.
> mask           : Subnet mask.
> gateway     : Gateway IP to redirect packet to next hop.
> 
> 
> Syntax:
> 
> <node_name> map port <dbdf> queue <rxq> core <core_id>
> 
> Where:
> 
> node_name : Node name which will be receiving packets as per above
> mapping. Currently only supported node id ethdev_rx.
> rxq               : Rx queue id which is to be mapped
> core_id        : Core ID to be mapped where <node_name> instance will be
> running.
> Run use case
> =========
> Command under this section can be used to run the application and start to
> receive and transmit  packets using graph walk.
> 
> Syntax:
> 
> graph start
> 
> 
> 
> Note:
>     <> : Mandatory fields
>     [] : Optional fields
>     ;  : To add any comments, Strings added after semicolon is not used by any
> usecase
> 
> 
> Example use case: l3fwd
> ================
> ;Configure usecase
> graph l3fwd default 0xff
> 
> ;global specific configuration
> mempool mempool0 size 2046 buffers 32000 cache 256 cpu 0
> ethdev 0002:02:00.0 rxq 1 txq 1 mempool0 promiscuous off
> ethdev 0002:03:00.0 rxq 1 txq 1 mempool0 promiscuous off
> ip4 addr add 10.0.2.1/24 0002:02:00.0
> ip4 addr add 10.0.3.1/24 0002:03:00.0
> neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
> neigh add ipv4 10.0.2.5 62:20:DA:4F:68:70
> neigh add ipv4 10.0.3.2 72:20:DA:4F:68:70
> neigh add ipv4 10.0.3.5 82:20:DA:4F:68:70
> 
> ;node specific configuration
> ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> ipv4_lookup route add ipv4 10.0.3.0 netmask 255.255.255.0 via 10.0.3.1
> 
> 
> ethdev_rx  map port 0002:02:00.0 queue 0 core 6
> ethdev_rx  map port 0002:03:00.0 queue 1 core 5
> 
> ;Run usecase
> graph start
> 


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v3 1/1] app/graph: add example for different usecases
  2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
@ 2023-09-09  1:18       ` Nithin Kumar Dabilpuram
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
  1 sibling, 0 replies; 182+ messages in thread
From: Nithin Kumar Dabilpuram @ 2023-09-09  1:18 UTC (permalink / raw)
  To: Sunil Kumar Kori, Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla
  Cc: dev

Please see inline.

> -----Original Message-----
> From: skori@marvell.com <skori@marvell.com>
> Sent: Friday, September 8, 2023 4:19 PM
> To: Thomas Monjalon <thomas@monjalon.net>; Sunil Kumar Kori <skori@marvell.com>;
> Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Cc: dev@dpdk.org
> Subject: [EXT] [PATCH v3 1/1] app/graph: add example for different usecases
> 
> External Email
> 
> ----------------------------------------------------------------------
> From: Sunil Kumar Kori <skori@marvell.com>
> 
> Current l3fwd-graph application only validates l3fwd use case.
> To scale up this, new application will be added with a framework
> to run as user's provided usecases.
> 
> Required configuration and use cases details are fetched via a
> static .cli file which will be used to create a graph for
> requested uscases.
> 
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  MAINTAINERS                                  |   7 +
>  app/graph/cli.c                              | 208 ++++++
>  app/graph/cli.h                              |  48 ++
>  app/graph/cli_priv.h                         |  19 +
>  app/graph/conn.c                             | 284 +++++++++
>  app/graph/conn.h                             |  46 ++
>  app/graph/ethdev.c                           | 632 +++++++++++++++++++
>  app/graph/ethdev.h                           |  28 +
>  app/graph/ethdev_priv.h                      |  46 ++
>  app/graph/ethdev_rx.c                        | 139 ++++
>  app/graph/ethdev_rx.h                        |  32 +
>  app/graph/ethdev_rx_priv.h                   |  23 +
>  app/graph/examples/l3fwd.cli                 |  87 +++
>  app/graph/graph.c                            | 383 +++++++++++
>  app/graph/graph.h                            |  11 +
>  app/graph/graph_priv.h                       |  32 +
>  app/graph/ip4_route.c                        | 146 +++++
>  app/graph/ip6_route.c                        | 154 +++++
>  app/graph/l3fwd.c                            | 152 +++++
>  app/graph/l3fwd.h                            |  11 +
>  app/graph/main.c                             | 201 ++++++
>  app/graph/mempool.c                          | 134 ++++
>  app/graph/mempool.h                          |  18 +
>  app/graph/mempool_priv.h                     |  16 +
>  app/graph/meson.build                        |  25 +
>  app/graph/module_api.h                       |  33 +
>  app/graph/neigh.c                            | 269 ++++++++
>  app/graph/neigh.h                            |  11 +
>  app/graph/neigh_priv.h                       |  22 +
>  app/graph/route.h                            |  30 +
>  app/graph/utils.c                            | 155 +++++
>  app/graph/utils.h                            |  14 +
>  app/meson.build                              |   1 +
>  doc/guides/tools/graph.rst                   | 171 +++++
>  doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++
>  doc/guides/tools/index.rst                   |   1 +
>  36 files changed, 3799 insertions(+)
>  create mode 100644 app/graph/cli.c
>  create mode 100644 app/graph/cli.h
>  create mode 100644 app/graph/cli_priv.h
>  create mode 100644 app/graph/conn.c
>  create mode 100644 app/graph/conn.h
>  create mode 100644 app/graph/ethdev.c
>  create mode 100644 app/graph/ethdev.h
>  create mode 100644 app/graph/ethdev_priv.h
>  create mode 100644 app/graph/ethdev_rx.c
>  create mode 100644 app/graph/ethdev_rx.h
>  create mode 100644 app/graph/ethdev_rx_priv.h
>  create mode 100644 app/graph/examples/l3fwd.cli
>  create mode 100644 app/graph/graph.c
>  create mode 100644 app/graph/graph.h
>  create mode 100644 app/graph/graph_priv.h
>  create mode 100644 app/graph/ip4_route.c
>  create mode 100644 app/graph/ip6_route.c
>  create mode 100644 app/graph/l3fwd.c
>  create mode 100644 app/graph/l3fwd.h
>  create mode 100644 app/graph/main.c
>  create mode 100644 app/graph/mempool.c
>  create mode 100644 app/graph/mempool.h
>  create mode 100644 app/graph/mempool_priv.h
>  create mode 100644 app/graph/meson.build
>  create mode 100644 app/graph/module_api.h
>  create mode 100644 app/graph/neigh.c
>  create mode 100644 app/graph/neigh.h
>  create mode 100644 app/graph/neigh_priv.h
>  create mode 100644 app/graph/route.h
>  create mode 100644 app/graph/utils.c
>  create mode 100644 app/graph/utils.h
>  create mode 100644 doc/guides/tools/graph.rst
>  create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg
> 

[Nithin] Split to multiple smaller patches

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 698608cdb2..7f149bd060 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1806,6 +1806,13 @@ F: dts/
>  F: devtools/dts-check-format.sh
>  F: doc/guides/tools/dts.rst
> 
> +Graph application
> +M: Sunil Kumar Kori <skori@marvell.com>
> +M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> +F: app/graph/
> +F: doc/guides/tools/graph.rst
> +F: doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
> 
>  Other Example Applications
>  --------------------------
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> new file mode 100644
> index 0000000000..237fa8008f
> --- /dev/null
> +++ b/app/graph/cli.c
> @@ -0,0 +1,208 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_malloc.h>
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "cli_priv.h"
> +#include "module_api.h"
> +
> +#define CMD_MAX_TOKENS 256
> +#define MAX_LINE_SIZE 2048
> +
> +static struct cli_node_head module_list = STAILQ_HEAD_INITIALIZER(module_list);
> +
> +#define PARSE_DELIMITER " \f\n\r\t\v"
> +
> +static int
> +tokenize_string_parse(char *string, char *tokens[], uint32_t *n_tokens)
> +{
> +	uint32_t i;
> +
> +	if ((string == NULL) ||
> +		(tokens == NULL) ||
> +		(*n_tokens < 1))
> +		return -EINVAL;
> +
> +	for (i = 0; i < *n_tokens; i++) {
> +		tokens[i] = strtok_r(string, PARSE_DELIMITER, &string);
> +		if (tokens[i] == NULL)
> +			break;
> +	}
> +
> +	if ((i == *n_tokens) && strtok_r(string, PARSE_DELIMITER, &string))
> +		return -E2BIG;
> +
> +	*n_tokens = i;
> +	return 0;
> +}
> +
> +static int
> +is_comment(char *in)
> +{
> +	if ((strlen(in) && index("!#%;", in[0])) ||
> +		(strncmp(in, "//", 2) == 0) ||
> +		(strncmp(in, "--", 2) == 0))
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static bool
> +module_list_has_cmd_registered(const char *cmd)
> +{
> +	struct cli_node *node;
> +
> +	STAILQ_FOREACH(node, &module_list, next) {
> +		if (strcmp(node->cmd, cmd) == 0) {
> +			rte_errno = EEXIST;
> +			return 1;
> +		}
> +	}
> +	return 0;
> +}
> +
> +void
> +cli_module_register(const struct cli_module *module)
> +{
> +	struct cli_node *node;
> +
> +	/* Check sanity */
> +	if (module == NULL || module->process == NULL) {
> +		rte_errno = EINVAL;
> +		return;
> +	}
> +
> +	/* Check for duplicate name */
> +	if (module_list_has_cmd_registered(module->cmd)) {
> +		printf("module %s is already registered\n", module->cmd);
> +		return;
> +	}
> +
> +	node = malloc(sizeof(struct cli_node));
> +	if (node == NULL) {
> +		rte_errno = ENOMEM;
> +		return;
> +	}
> +
> +	/* Initialize the node */
> +	if (rte_strscpy(node->cmd, module->cmd, APP_CLI_CMD_NAME_SIZE) < 0) {
> +		free(node);
> +		return;
> +	}
> +	node->process = module->process;
> +	node->usage = module->usage;
> +
> +	/* Add the node at tail */
> +	STAILQ_INSERT_TAIL(&module_list, node, next);
> +}
> +
> +void
> +cli_process(char *in, char *out, size_t out_size, void *obj)
> +{
> +	char *tokens[CMD_MAX_TOKENS];
> +	struct cli_node *node;
> +	uint32_t n_tokens;
> +	int rc;
> +
> +	if (is_comment(in))
> +		return;
> +
> +	n_tokens = RTE_DIM(tokens);
> +	rc = tokenize_string_parse(in, tokens, &n_tokens);
> +	if (rc) {
> +		snprintf(out, out_size, MSG_ARG_TOO_MANY, "");
> +		return;
> +	}
> +
> +	if (n_tokens == 0)
> +		return;
> +
> +	if ((n_tokens == 1) && strcmp(tokens[0], "help") == 0) {
> +		STAILQ_FOREACH(node, &module_list, next) {
> +			node->usage(tokens, n_tokens, out, out_size, obj);
> +		}
> +		return;
> +	}
> +
> +	if ((n_tokens >= 2) && strcmp(tokens[0], "help") == 0) {
> +		STAILQ_FOREACH(node, &module_list, next) {
> +			if (strcmp(node->cmd, tokens[1]) == 0) {
> +				node->usage(tokens, n_tokens, out, out_size, obj);
> +				return;
> +			}
> +		}
> +		snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> +		return;
> +	}
> +
> +	STAILQ_FOREACH(node, &module_list, next) {
> +		if (strcmp(node->cmd, tokens[0]) == 0) {
> +			rc = node->process(tokens, n_tokens, out, out_size, obj);
> +			if (rc < 0)
> +				snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +			return;
> +		}
> +	}
> +
> +	snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> +}
> +
> +int
> +cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max, void *obj)
> +{
> +	char *msg_in = NULL, *msg_out = NULL;
> +	FILE *f = NULL;
> +
> +	/* Check input arguments */
> +	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
> +	    (msg_out_len_max == 0))
> +		return -EINVAL;
> +
> +	msg_in = malloc(msg_in_len_max + 1);
> +	msg_out = malloc(msg_out_len_max + 1);
> +	if ((msg_in == NULL) || (msg_out == NULL)) {
> +		free(msg_out);
> +		free(msg_in);
> +		return -ENOMEM;
> +	}
> +
> +	/* Open input file */
> +	f = fopen(file_name, "r");
> +	if (f == NULL) {
> +		free(msg_out);
> +		free(msg_in);
> +		return -EIO;
> +	}
> +
> +	/* Read file */
> +	while (1) {
> +		if (fgets(msg_in, msg_in_len_max + 1, f) == NULL)
> +			break;
> +
> +		msg_out[0] = 0;
> +
> +		cli_process(msg_in, msg_out, msg_out_len_max, obj);
> +
> +		if (strlen(msg_out))
> +			printf("%s", msg_out);
> +	}
> +
> +	/* Close file */
> +	fclose(f);
> +	free(msg_out);
> +	free(msg_in);
> +	return 0;
> +}
> diff --git a/app/graph/cli.h b/app/graph/cli.h
> new file mode 100644
> index 0000000000..2bd89f3d1f
> --- /dev/null
> +++ b/app/graph/cli.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_H
> +#define APP_GRAPH_CLI_H
> +
> +/* Macros */
> +#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
> +#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
> +#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
> +#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
> +#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
> +#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
> +#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
> +#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
> +#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
> +#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
> +#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
> +
> +#define APP_CLI_CMD_NAME_SIZE	64
> +
> +/* Typedefs */
> +typedef int (*cli_module_t)(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +			     void *obj);
> +
> +/* Structures */
> +struct cli_module {
> +	char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command to be
> registered. */
> +	cli_module_t process; /**< Command process function. */
> +	cli_module_t usage; /**< Help command process function. */
> +};
> +
> +/* APIs */
> +void cli_module_register(const struct cli_module *module);
> +
> +#define CLI_REGISTER(module)			\
> +	RTE_INIT(cli_register_##module)		\
> +	{					\
> +		cli_module_register(&module);	\
> +	}
> +
> +void cli_process(char *in, char *out, size_t out_size, void *arg);
> +
> +int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max,
> +		       void *arg);
> +
> +#endif
> diff --git a/app/graph/cli_priv.h b/app/graph/cli_priv.h
> new file mode 100644
> index 0000000000..9ecc89c353
> --- /dev/null
> +++ b/app/graph/cli_priv.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_PRIV_H
> +#define APP_GRAPH_CLI_PRIV_H
> +
> +#include "cli.h"
> +
> +struct cli_node {
> +	STAILQ_ENTRY(cli_node) next;	 /**< Next node in the list. */
> +	char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command. */
> +	cli_module_t process;		 /**< Command process function. */
> +	cli_module_t usage;		/**< Help command process function. */
> +};
> +
> +STAILQ_HEAD(cli_node_head, cli_node);
> +
> +#endif
> diff --git a/app/graph/conn.c b/app/graph/conn.c
> new file mode 100644
> index 0000000000..dabc8deca2
> --- /dev/null
> +++ b/app/graph/conn.c
> @@ -0,0 +1,284 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <netinet/in.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#include "module_api.h"
> +
> +#define MSG_CMD_TOO_LONG "Command too long."
> +
> +static int
> +data_event_handle(struct conn *conn, int fd_client)
> +{
> +	ssize_t len, i, rc = 0;
> +
> +	/* Read input message */
> +	len = read(fd_client, conn->buf, conn->buf_size);
> +	if (len == -1) {
> +		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +			return 0;
> +
> +		return -1;
> +	}
> +
> +	if (len == 0)
> +		return rc;
> +
> +	/* Handle input messages */
> +	for (i = 0; i < len; i++) {
> +		if (conn->buf[i] == '\n') {
> +			size_t n;
> +
> +			conn->msg_in[conn->msg_in_len] = 0;
> +			conn->msg_out[0] = 0;
> +
> +			conn->msg_handle(conn->msg_in, conn->msg_out, conn-
> >msg_out_len_max,
> +					 conn->msg_handle_arg);
> +
> +			n = strlen(conn->msg_out);
> +			if (n) {
> +				rc = write(fd_client, conn->msg_out, n);
> +				if (rc == -1)
> +					goto exit;
> +			}
> +
> +			conn->msg_in_len = 0;
> +		} else if (conn->msg_in_len < conn->msg_in_len_max) {
> +			conn->msg_in[conn->msg_in_len] = conn->buf[i];
> +			conn->msg_in_len++;
> +		} else {
> +			rc = write(fd_client, MSG_CMD_TOO_LONG,
> strlen(MSG_CMD_TOO_LONG));
> +			if (rc == -1)
> +				goto exit;
> +
> +			conn->msg_in_len = 0;
> +		}
> +	}
> +
> +	/* Write prompt */
> +	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +	rc = (rc == -1) ? -1 : 0;
> +
> +exit:
> +	return rc;
> +}
> +
> +static int
> +control_event_handle(struct conn *conn, int fd_client)
> +{
> +	int rc;
> +
> +	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
> +	if (rc == -1)
> +		goto exit;
> +
> +	rc = close(fd_client);
> +	if (rc == -1)
> +		goto exit;
> +
> +	rc = 0;
> +
> +exit:
> +	return rc;
> +}
> +
> +struct conn *
> +conn_init(struct conn_params *p)
> +{
> +	int fd_server, fd_client_group, rc;
> +	struct sockaddr_in server_address;
> +	struct conn *conn = NULL;
> +
> +	memset(&server_address, 0, sizeof(server_address));
> +
> +	/* Check input arguments */
> +	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr ==
> NULL) ||
> +	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0)
> ||
> +	    (p->msg_handle == NULL))
> +		goto exit;
> +
> +	rc = inet_aton(p->addr, &server_address.sin_addr);
> +	if (rc == 0)
> +		goto exit;
> +
> +	/* Memory allocation */
> +	conn = calloc(1, sizeof(struct conn));
> +	if (conn == NULL)
> +		goto exit;
> +
> +	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
> +	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
> +	conn->buf = calloc(1, p->buf_size);
> +	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
> +	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
> +
> +	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL)
> ||
> +	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
> +		conn_free(conn);
> +		conn = NULL;
> +		goto exit;
> +	}
> +
> +	/* Server socket */
> +	server_address.sin_family = AF_INET;
> +	server_address.sin_port = htons(p->port);
> +
> +	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
> +	if (fd_server == -1) {
> +		conn_free(conn);
> +		conn = NULL;
> +		goto exit;
> +	}
> +
> +	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
> +	if (rc == -1) {
> +		conn_free(conn);
> +		close(fd_server);
> +		conn = NULL;
> +		goto exit;
> +	}
> +
> +	rc = listen(fd_server, 16);
> +	if (rc == -1) {
> +		conn_free(conn);
> +		close(fd_server);
> +		conn = NULL;
> +		goto exit;
> +	}
> +
> +	/* Client group */
> +	fd_client_group = epoll_create(1);
> +	if (fd_client_group == -1) {
> +		conn_free(conn);
> +		close(fd_server);
> +		conn = NULL;
> +		goto exit;
> +	}
> +
> +	/* Fill in */
> +	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> +	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
> +	conn->buf_size = p->buf_size;
> +	conn->msg_in_len_max = p->msg_in_len_max;
> +	conn->msg_out_len_max = p->msg_out_len_max;
> +	conn->msg_in_len = 0;
> +	conn->fd_server = fd_server;
> +	conn->fd_client_group = fd_client_group;
> +	conn->msg_handle = p->msg_handle;
> +	conn->msg_handle_arg = p->msg_handle_arg;
> +
> +exit:
> +	return conn;
> +}
> +
> +void
> +conn_free(struct conn *conn)
> +{
> +	if (conn == NULL)
> +		return;
> +
> +	if (conn->fd_client_group)
> +		close(conn->fd_client_group);
> +
> +	if (conn->fd_server)
> +		close(conn->fd_server);
> +
> +	free(conn->msg_out);
> +	free(conn->msg_in);
> +	free(conn->prompt);
> +	free(conn->welcome);
> +	free(conn);
> +}
> +
> +int
> +conn_req_poll(struct conn *conn)
> +{
> +	struct sockaddr_in client_address;
> +	socklen_t client_address_length;
> +	struct epoll_event event;
> +	int fd_client, rc;
> +
> +	/* Check input arguments */
> +	if (conn == NULL)
> +		return -1;
> +
> +	/* Server socket */
> +	client_address_length = sizeof(client_address);
> +	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
> +			    &client_address_length, SOCK_NONBLOCK);
> +	if (fd_client == -1) {
> +		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +			return 0;
> +
> +		return -1;
> +	}
> +
> +	/* Client group */
> +	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
> +	event.data.fd = fd_client;
> +
> +	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
> +	if (rc == -1) {
> +		close(fd_client);
> +		goto exit;
> +	}
> +
> +	/* Client */
> +	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
> +	if (rc == -1) {
> +		close(fd_client);
> +		goto exit;
> +	}
> +
> +	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +	if (rc == -1) {
> +		close(fd_client);
> +		goto exit;
> +	}
> +
> +	rc = 0;
> +
> +exit:
> +	return rc;
> +}
> +
> +int
> +conn_msg_poll(struct conn *conn)
> +{
> +	int fd_client, rc, rc_data = 0, rc_control = 0;
> +	struct epoll_event event;
> +
> +	/* Check input arguments */
> +	if (conn == NULL)
> +		return -1;
> +
> +	/* Client group */
> +	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
> +	if ((rc == -1) || rc == 0)
> +		return rc;
> +
> +	fd_client = event.data.fd;
> +
> +	/* Data available */
> +	if (event.events & EPOLLIN)
> +		rc_data = data_event_handle(conn, fd_client);
> +
> +	/* Control events */
> +	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
> +		rc_control = control_event_handle(conn, fd_client);
> +
> +	if (rc_data || rc_control)
> +		return -1;
> +
> +	return 0;
> +}
> diff --git a/app/graph/conn.h b/app/graph/conn.h
> new file mode 100644
> index 0000000000..770964cf4c
> --- /dev/null
> +++ b/app/graph/conn.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CONN_H
> +#define APP_GRAPH_CONN_H
> +
> +#define CONN_WELCOME_LEN_MAX 1024
> +#define CONN_PROMPT_LEN_MAX 16
> +
> +typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t
> msg_out_len_max, void *arg);
> +
> +struct conn {
> +	char *welcome;
> +	char *prompt;
> +	char *buf;
> +	char *msg_in;
> +	char *msg_out;
> +	size_t buf_size;
> +	size_t msg_in_len_max;
> +	size_t msg_out_len_max;
> +	size_t msg_in_len;
> +	int fd_server;
> +	int fd_client_group;
> +	conn_msg_handle_t msg_handle;
> +	void *msg_handle_arg;
> +};
> +
> +struct conn_params {
> +	const char *welcome;
> +	const char *prompt;
> +	const char *addr;
> +	uint16_t port;
> +	size_t buf_size;
> +	size_t msg_in_len_max;
> +	size_t msg_out_len_max;
> +	conn_msg_handle_t msg_handle;
> +	void *msg_handle_arg;
> +};
> +
> +struct conn *conn_init(struct conn_params *p);
> +void conn_free(struct conn *conn);
> +int conn_req_poll(struct conn *conn);
> +int conn_msg_poll(struct conn *conn);
> +
> +#endif
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> new file mode 100644
> index 0000000000..840a8ca42f
> --- /dev/null
> +++ b/app/graph/ethdev.c
> @@ -0,0 +1,632 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_bitops.h>
> +#include <rte_ethdev.h>
> +#include <rte_mempool.h>
> +
> +#include "ethdev_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
> +
> +static const char
> +cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
> +
> +static const char
> +cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues>
> <mempool_name> "
> +		    "[mtu <mtu_sz>]";
> +static const char
> +cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
> +
> +static const char
> +cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask
> <mask>";
> +
> +static const char
> +cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask
> <mask>";
> +
> +static struct rte_eth_conf port_conf_default = {
> +	.link_speeds = 0,
> +	.rxmode = {
> +		.mq_mode = RTE_ETH_MQ_RX_NONE,
> +		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo
> frame MTU */
> +	},
> +	.rx_adv_conf = {
> +		.rss_conf = {
> +			.rss_key = NULL,
> +			.rss_key_len = 40,
> +			.rss_hf = 0,
> +		},
> +	},
> +	.txmode = {
> +		.mq_mode = RTE_ETH_MQ_TX_NONE,
> +	},
> +	.lpbk_mode = 0,
> +};
> +
> +uint32_t enabled_port_mask;
> +struct ethdev port_list[RTE_MAX_ETHPORTS];
> +
> +void *
> +ethdev_mempool_list_by_portid(uint16_t portid)
> +{
> +	if (portid >= RTE_MAX_ETHPORTS)
> +		return NULL;
> +
> +	return &port_list[portid].config.rx.mp;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip4(uint32_t ip)
> +{
> +	int portid = -EINVAL;
> +	int i;
> +
> +	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +		if ((port_list[i].ip4_addr.ip & route4[i].netmask) == (ip &
> route4[i].netmask))
> +			break;
> +	}
> +
> +	if (i == RTE_MAX_ETHPORTS)
> +		return portid;
> +
> +	return port_list[i].config.port_id;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip6(uint8_t *ip)
> +{
> +	int portid = -EINVAL;
> +	int i, j;
> +
> +	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +			if ((port_list[i].ip6_addr.ip[j] & route6[i].mask[j]) !=
> +			    (ip[j] & route6[i].mask[j]))
> +				break;
> +		}
> +
> +		if (j == ETHDEV_IPV6_ADDR_LEN)
> +			break;
> +	}
> +
> +	if (i == RTE_MAX_ETHPORTS)
> +		return portid;
> +
> +	return port_list[i].config.port_id;
> +}
> +
> +void
> +ethdev_stop(void)
> +{
> +	uint16_t portid;
> +	int rc;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +		printf("Closing port %d...", portid);
> +		rc = rte_eth_dev_stop(portid);
> +		if (rc != 0)
> +			printf("Failed to stop port %u: %s\n",
> +					portid, rte_strerror(-rc));
> +		rte_eth_dev_close(portid);
> +		printf(" Done\n");
> +	}
> +
> +	/* clean up the EAL */
> +	rte_eal_cleanup();
> +	printf("Bye...\n");
> +}
> +
> +void
> +ethdev_start(void)
> +{
> +	uint16_t portid;
> +	int rc;
> +
> +	RTE_ETH_FOREACH_DEV(portid)
> +	{
> +		if ((enabled_port_mask & (1 << portid)) == 0)
> +			continue;
> +
> +		rc = rte_eth_dev_start(portid);
> +		if (rc < 0)
> +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n",
> rc, portid);
> +	}
> +}
> +
> +
> +static int
> +ethdev_show(const char *name, char **out, size_t *out_size)
> +{
> +	uint16_t mtu = 0, port_id = 0;
> +	struct rte_eth_dev_info info;
> +	struct rte_eth_stats stats;
> +	struct rte_ether_addr addr;
> +	struct rte_eth_link link;
> +	uint32_t length;
> +	int rc;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +	if (rc < 0)
> +		return rc;
> +
> +	rte_eth_dev_info_get(port_id, &info);
> +	rte_eth_stats_get(port_id, &stats);
> +	rte_eth_macaddr_get(port_id, &addr);
> +	rte_eth_link_get(port_id, &link);
> +	rte_eth_dev_get_mtu(port_id, &mtu);
> +
> +	snprintf(*out, *out_size,
> +		 "%s: flags=<%s> mtu %u\n"
> +		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
> +		 "\tport# %u  speed %s\n"
> +		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
> +		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
> +		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
> +		 "\tTX errors %" PRIu64"\n\n",
> +		 name,
> +		 link.link_status ? "UP" : "DOWN",
> +		 mtu,
> +		 RTE_ETHER_ADDR_BYTES(&addr),
> +		 info.nb_rx_queues,
> +		 info.nb_tx_queues,
> +		 port_id,
> +		 rte_eth_link_speed_to_str(link.link_speed),
> +		 stats.ipackets,
> +		 stats.ibytes,
> +		 stats.ierrors,
> +		 stats.imissed,
> +		 stats.rx_nombuf,
> +		 stats.opackets,
> +		 stats.obytes,
> +		 stats.oerrors);
> +
> +	length = strlen(*out);
> +	*out_size -= length;
> +	*out += length;
> +	return 0;
> +}
> +
> +static int
> +ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
> +{
> +	uint16_t portid = 0;
> +	int rc;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &portid);
> +	if (rc < 0)
> +		return rc;
> +
> +	port_list[portid].ip4_addr.ip = config->ip;
> +	port_list[portid].ip4_addr.mask = config->mask;
> +	return 0;
> +}
> +
> +static int
> +ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
> +{
> +	uint16_t portid = 0;
> +	int rc, i;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &portid);
> +	if (rc < 0)
> +		return rc;
> +
> +	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +		port_list[portid].ip6_addr.ip[i] = config->ip[i];
> +		port_list[portid].ip6_addr.mask[i] = config->mask[i];
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +ethdev_prom_mode_config(const char *name, bool enable)
> +{
> +	uint16_t portid = 0;
> +	int rc;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &portid);
> +	if (rc < 0)
> +		return rc;
> +
> +	if (enable)
> +		rc = rte_eth_promiscuous_enable(portid);
> +	else
> +		rc = rte_eth_promiscuous_disable(portid);
> +
> +	if (rc < 0)
> +		return rc;
> +
> +	port_list[portid].config.promiscuous = enable;
> +	return 0;
> +}
> +
> +static int
> +ethdev_mtu_config(const char *name, uint32_t mtu)
> +{
> +	uint16_t portid = 0;
> +	int rc;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &portid);
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = rte_eth_dev_set_mtu(portid, mtu);
> +	if (rc < 0)
> +		return rc;
> +
> +	port_list[portid].config.mtu = mtu;
> +	return 0;
> +}
> +
> +static int
> +ethdev_process(const char *name, struct ethdev_config *params)
> +{
> +	struct rte_eth_dev_info port_info;
> +	struct rte_eth_conf port_conf;
> +	struct ethdev_rss_config *rss;
> +	struct rte_mempool *mempool;
> +	struct rte_ether_addr smac;
> +	int numa_node, rc;
> +	uint16_t port_id = 0;
> +	uint32_t i;
> +
> +	/* Check input params */
> +	if (!name || !name[0] || !params || !params->rx.n_queues || !params-
> >rx.queue_size ||
> +	    !params->tx.n_queues || !params->tx.queue_size)
> +		return -EINVAL;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +	if (rc)
> +		return -EINVAL;
> +
> +	rc = rte_eth_dev_info_get(port_id, &port_info);
> +	if (rc)
> +		return -EINVAL;
> +
> +	mempool = rte_mempool_lookup(params->rx.mempool_name);
> +	if (!mempool)
> +		return -EINVAL;
> +
> +	params->rx.mp = mempool;
> +
> +	rss = params->rx.rss;
> +	if (rss) {
> +		if (!port_info.reta_size || port_info.reta_size >
> RTE_ETH_RSS_RETA_SIZE_512)
> +			return -EINVAL;
> +
> +		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX)
> +			return -EINVAL;
> +
> +		for (i = 0; i < rss->n_queues; i++)
> +			if (rss->queue_id[i] >= port_info.max_rx_queues)
> +				return -EINVAL;
> +	}
> +
> +	/* Port */
> +	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
> +	if (rss) {
> +		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP |
> RTE_ETH_RSS_UDP;
> +
> +		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
> +		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf &
> port_info.flow_type_rss_offloads;
> +	}
> +
> +	numa_node = rte_eth_dev_socket_id(port_id);
> +	if (numa_node == SOCKET_ID_ANY)
> +		numa_node = 0;
> +
> +	if (params->mtu)
> +		port_conf.rxmode.mtu = params->mtu;
> +
> +	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
> +				       &port_conf);
> +	if (rc < 0)
> +		return -EINVAL;
> +
> +	rc = rte_eth_macaddr_get(port_id, &smac);
> +	if (rc < 0)
> +		return -EINVAL;
> +
> +	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
> +		smac.addr_bytes[0], smac.addr_bytes[1],
> +		smac.addr_bytes[2], smac.addr_bytes[3],
> +		smac.addr_bytes[4], smac.addr_bytes[5]);
> +
> +	/* Port RX */
> +	for (i = 0; i < params->rx.n_queues; i++) {
> +		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size,
> numa_node, NULL,
> +			mempool);
> +		if (rc < 0)
> +			return -EINVAL;
> +	}
> +
> +	/* Port TX */
> +	for (i = 0; i < params->tx.n_queues; i++) {
> +		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size,
> numa_node, NULL);
> +		if (rc < 0)
> +			return -EINVAL;
> +	}
> +
> +	memcpy(&port_list[port_id].config, params, sizeof(struct ethdev_config));
> +	memcpy(port_list[port_id].config.dev_name, name, strlen(name));
> +	port_list[port_id].config.port_id = port_id;
> +	enabled_port_mask |= RTE_BIT32(port_id);
> +	return 0;
> +}
> +
> +static int
> +cmd_ethdev_mtu(char **tokens, uint32_t n_tokens __rte_unused, char *out, size_t
> out_size,
> +	       void *obj __rte_unused)
> +{
> +	int rc = -EINVAL;
> +	uint32_t mtu = 0;
> +
> +	if (n_tokens != 4) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		return rc;
> +	}
> +
> +	if (parser_uint32_read(&mtu, tokens[3]) != 0) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> +		return rc;
> +	}
> +
> +	rc = ethdev_mtu_config(tokens[1], mtu);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +	return rc;
> +}
> +
> +static int
> +cmd_ethdev_prom_mode(char **tokens, uint32_t n_tokens __rte_unused, char *out,
> size_t out_size,
> +		     void *obj __rte_unused)
> +{
> +	bool enable = false;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 4) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		return rc;
> +	}
> +
> +	if (strcmp(tokens[3], "on") == 0)
> +		enable = true;
> +
> +	rc = ethdev_prom_mode_config(tokens[1], enable);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +	return rc;
> +}
> +
> +static int
> +cmd_ip4_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	struct ipv4_addr_config config;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 8) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[3], "addr")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[4], "add")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&config.ip, tokens[5])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[6], "netmask")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&config.mask, tokens[7])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +		goto exit;
> +	}
> +
> +	rc = ethdev_ip4_addr_add(tokens[1], &config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static int
> +cmd_ip6_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	struct ipv6_addr_config config;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 8) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[3], "addr")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[4], "add")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(config.ip, tokens[5])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[6], "netmask")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(config.mask, tokens[7])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +		goto exit;
> +	}
> +
> +	rc = ethdev_ip6_addr_add(tokens[1], &config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static int
> +cmd_ethdev_show(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +	void *obj __rte_unused)
> +{
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 3) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		return rc;
> +	}
> +
> +	rc = ethdev_show(tokens[1], &out, &out_size);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +	return rc;
> +}
> +
> +static int
> +cmd_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	struct ethdev_config config;
> +	char *name;
> +	int rc;
> +
> +	if (n_tokens < 7) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		return -EINVAL;
> +	}
> +
> +	memset(&config, 0, sizeof(struct ethdev_config));
> +	name = tokens[1];
> +
> +	if (strcmp(tokens[2], "rxq") != 0) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> +		return -EINVAL;
> +	}
> +
> +	if (parser_uint32_read(&config.rx.n_queues, tokens[3]) != 0) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> +		return -EINVAL;
> +	}
> +
> +	if (strcmp(tokens[4], "txq") != 0) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "txq");
> +		return -EINVAL;
> +	}
> +
> +	if (parser_uint32_read(&config.tx.n_queues, tokens[5]) != 0) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> +		return -EINVAL;
> +	}
> +
> +	mempcpy(config.rx.mempool_name, tokens[6], strlen(tokens[6]));
> +
> +	if (n_tokens > 7) {
> +		if (strcmp(tokens[7], "mtu") != 0) {
> +			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "mtu");
> +			return -EINVAL;
> +		}
> +
> +		if (parser_uint32_read(&config.mtu, tokens[8]) != 0) {
> +			snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
> +	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
> +
> +	rc = ethdev_process(name, &config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +	return rc;
> +}
> +
> +static int
> +cli_ethdev_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> +		   size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "----------------------------- ethdev command help -----------------------------");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip4_addr_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip6_addr_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_prom_mode_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_mtu_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_show_help);
> +
> +	return 0;
> +}
> +
> +static int
> +cli_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
> +{
> +	if (strcmp(tokens[2], "show") == 0)
> +		return cmd_ethdev_show(tokens, n_tokens, out, out_size, obj);
> +	else if (strcmp(tokens[2], "mtu") == 0)
> +		return cmd_ethdev_mtu(tokens, n_tokens, out, out_size, obj);
> +	else if (strcmp(tokens[2], "promiscuous") == 0)
> +		return cmd_ethdev_prom_mode(tokens, n_tokens, out, out_size, obj);
> +	else if (strcmp(tokens[2], "ip4") == 0)
> +		return cmd_ip4_addr(tokens, n_tokens, out, out_size, obj);
> +	else if (strcmp(tokens[2], "ip6") == 0)
> +		return cmd_ip6_addr(tokens, n_tokens, out, out_size, obj);
> +	else
> +		return cmd_ethdev(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module ethdev = {
> +	.cmd = "ethdev",
> +	.process = cli_ethdev,
> +	.usage = cli_ethdev_help,
> +};
> +
> +CLI_REGISTER(ethdev);
> diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
> new file mode 100644
> index 0000000000..9c3de49826
> --- /dev/null
> +++ b/app/graph/ethdev.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_H
> +#define APP_GRAPH_ETHDEV_H
> +
> +#define ETHDEV_IPV6_ADDR_LEN	16
> +
> +struct ipv4_addr_config {
> +	uint32_t ip;
> +	uint32_t mask;
> +};
> +
> +struct ipv6_addr_config {
> +	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
> +};
> +
> +extern uint32_t enabled_port_mask;
> +
> +void ethdev_start(void);
> +void ethdev_stop(void);
> +void *ethdev_mempool_list_by_portid(uint16_t portid);
> +int16_t ethdev_portid_by_ip4(uint32_t ip);
> +int16_t ethdev_portid_by_ip6(uint8_t *ip);
> +
> +#endif
> diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
> new file mode 100644
> index 0000000000..1026c2e5b6
> --- /dev/null
> +++ b/app/graph/ethdev_priv.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_PRIV_H
> +#define APP_GRAPH_ETHDEV_PRIV_H
> +
> +#include "ethdev.h"
> +
> +#define ETHDEV_RXQ_RSS_MAX	16
> +#define ETHDEV_RX_DESC_DEFAULT 1024
> +#define ETHDEV_TX_DESC_DEFAULT 1024
> +
> +struct ethdev_rss_config {
> +	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
> +	uint32_t n_queues;
> +};
> +
> +struct ethdev_config {
> +	char dev_name[RTE_ETH_NAME_MAX_LEN];
> +	uint16_t port_id;
> +
> +	struct {
> +		uint32_t n_queues;
> +		uint32_t queue_size;
> +		char mempool_name[RTE_MEMPOOL_NAMESIZE];
> +		struct rte_mempool *mp;
> +		struct ethdev_rss_config *rss;
> +	} rx;
> +
> +	struct {
> +		uint32_t n_queues;
> +		uint32_t queue_size;
> +	} tx;
> +
> +	int promiscuous;
> +	uint32_t mtu;
> +};
> +
> +struct ethdev {
> +	struct ethdev_config config;
> +	struct ipv4_addr_config ip4_addr;
> +	struct ipv6_addr_config ip6_addr;
> +};
> +
> +#endif
> diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
> new file mode 100644
> index 0000000000..d706d145c1
> --- /dev/null
> +++ b/app/graph/ethdev_rx.c
> @@ -0,0 +1,139 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_ethdev.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core
> <core_id>";
> +
> +static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +struct lcore_params *lcore_params = lcore_params_array;
> +struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +uint16_t nb_lcore_params;
> +
> +static void
> +rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
> +{
> +	uint8_t n_rx_queue;
> +
> +	n_rx_queue = lcore_conf[core].n_rx_queue;
> +	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
> +	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
> +	lcore_conf[core].n_rx_queue++;
> +}
> +
> +uint8_t
> +ethdev_rx_num_rx_queues_get(uint16_t port)
> +{
> +	int queue = -1;
> +	uint16_t i;
> +
> +	for (i = 0; i < nb_lcore_params; ++i) {
> +		if (lcore_params[i].port_id == port) {
> +			if (lcore_params[i].queue_id == queue + 1)
> +				queue = lcore_params[i].queue_id;
> +			else
> +				rte_exit(EXIT_FAILURE,
> +					 "Queue ids of the port %d must be"
> +					 " in sequence and must start with 0\n",
> +					 lcore_params[i].port_id);
> +		}
> +	}
> +
> +	return (uint8_t)(++queue);
> +}
> +
> +static int
> +ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
> +{
> +	uint16_t port_id;
> +	int rc;
> +
> +	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
> +		return -EINVAL;
> +
> +	rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +	if (rc)
> +		return -EINVAL;
> +
> +	rx_map_configure(port_id, queue, core);
> +
> +	lcore_params_array[nb_lcore_params].port_id = port_id;
> +	lcore_params_array[nb_lcore_params].queue_id = queue;
> +	lcore_params_array[nb_lcore_params].lcore_id = core;
> +	nb_lcore_params++;
> +	return 0;
> +}
> +
> +static int
> +cli_ethdev_rx_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> +		   size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "---------------------------- ethdev_rx command help ---------------------------");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ethdev_rx_help);
> +	return 0;
> +}
> +
> +static int
> +cli_ethdev_rx(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	char name[RTE_ETH_NAME_MAX_LEN];
> +	uint32_t core_id, queue;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 8) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	strcpy(name, tokens[3]);
> +
> +	if (strcmp(tokens[4], "queue") != 0) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&queue, tokens[5]) != 0) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[6], "core") != 0) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "core_id");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&core_id, tokens[7]) != 0) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> +		goto exit;
> +	}
> +
> +	rc = ethdev_rx_map_add(name, queue, core_id);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static struct cli_module ethdev_rx = {
> +	.cmd = "ethdev_rx",
> +	.process = cli_ethdev_rx,
> +	.usage = cli_ethdev_rx_help,
> +};
> +
> +CLI_REGISTER(ethdev_rx);
> diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
> new file mode 100644
> index 0000000000..d2c18f545f
> --- /dev/null
> +++ b/app/graph/ethdev_rx.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_H
> +#define APP_GRAPH_ETHDEV_RX_H
> +
> +#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
> +#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
> +
> +struct lcore_rx_queue {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +struct lcore_conf {
> +	uint16_t n_rx_queue;
> +	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
> +	struct rte_graph *graph;
> +	char name[RTE_GRAPH_NAMESIZE];
> +	rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +extern struct lcore_params *lcore_params;
> +extern uint16_t nb_lcore_params;
> +
> +#endif
> diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
> new file mode 100644
> index 0000000000..d714f83739
> --- /dev/null
> +++ b/app/graph/ethdev_rx_priv.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
> +#define APP_GRAPH_ETHDEV_RX_PRIV_H
> +
> +#include <stdint.h>
> +
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#define MAX_RX_QUEUE_PER_PORT 128
> +#define MAX_JUMBO_PKT_LEN  9600
> +#define NB_SOCKETS 8
> +
> +struct lcore_params {
> +	uint16_t port_id;
> +	uint8_t queue_id;
> +	uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +#endif
> diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
> new file mode 100644
> index 0000000000..9986e1b73e
> --- /dev/null
> +++ b/app/graph/examples/l3fwd.cli
> @@ -0,0 +1,87 @@
> +; SPDX-License-Identifier: BSD-3-Clause
> +; Copyright(c) 2023 Marvell.
> +
> +;
> +; Graph configuration for given usecase
> +;
> +graph l3fwd coremask ff model default
> +
> +;
> +; Mempools to be attached with ethdev
> +;
> +mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
> +
> +;
> +; DPDK devices and configuration.
> +;
> +; Note: Customize the parameters below to match your setup.
> +;
> +ethdev 0002:04:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:05:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:06:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:07:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:04:00.0 mtu 1700
> +ethdev 0002:05:00.0 promiscuous on
> +
> +;
> +; IPv4 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
> +ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
> +ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
> +ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
> +
> +;
> +; IPv6 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +
> +;
> +; IPv4 routes which are installed to ipv4_lookup node for LPM processing
> +;
> +ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> +ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
> +ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
> +ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
> +
> +;
> +; IPv6 routes which are installed to ipv6_lookup node for LPM processing
> +;
> +ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> +ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> +ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> +ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> +
> +;
> +; Peer MAC and IPv4 address mapping
> +;
> +neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
> +neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
> +neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
> +neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
> +
> +;
> +; Peer MAC and IPv6 address mapping
> +;
> +neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
> +neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
> +neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
> +neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
> +
> +;
> +; Port-Queue-Core mapping for ethdev_rx node
> +;
> +ethdev_rx map port 0002:04:00.0 queue 0 core 1
> +ethdev_rx map port 0002:05:00.0 queue 0 core 2
> +ethdev_rx map port 0002:06:00.0 queue 0 core 3
> +ethdev_rx map port 0002:07:00.0 queue 0 core 4
> +
> +;
> +; Graph start command to create graph.
> +;
> +; Note: No more command should come after this.
> +;
> +graph start
> diff --git a/app/graph/graph.c b/app/graph/graph.c
> new file mode 100644
> index 0000000000..8c75574ecd
> --- /dev/null
> +++ b/app/graph/graph.c
> @@ -0,0 +1,383 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_graph_worker.h>
> +#include <rte_log.h>
> +
> +#include "graph_priv.h"
> +#include "module_api.h"
> +
> +#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
> +
> +static const char
> +cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
> +		   "model <rtc | mcd | default>";
> +
> +static const char * const supported_usecases[] = {"l3fwd"};
> +struct graph_config graph_config;
> +
> +/* Check the link rc of all ports in up to 9s, and print them finally */
> +static void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
> +	uint8_t count, all_ports_up, print_flag = 0;
> +	struct rte_eth_link link;
> +	uint16_t portid;
> +	int rc;
> +
> +	printf("\nChecking link rc");
> +	fflush(stdout);
> +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +		if (force_quit)
> +			return;
> +
> +		all_ports_up = 1;
> +		RTE_ETH_FOREACH_DEV(portid)
> +		{
> +			if (force_quit)
> +				return;
> +
> +			if ((port_mask & (1 << portid)) == 0)
> +				continue;
> +
> +			memset(&link, 0, sizeof(link));
> +			rc = rte_eth_link_get_nowait(portid, &link);
> +			if (rc < 0) {
> +				all_ports_up = 0;
> +				if (print_flag == 1)
> +					printf("Port %u link get failed: %s\n",
> +					       portid, rte_strerror(-rc));
> +				continue;
> +			}
> +
> +			/* Print link rc if flag set */
> +			if (print_flag == 1) {
> +				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
> +					&link);
> +				printf("Port %d %s\n", portid, link_rc_text);
> +				continue;
> +			}
> +
> +			/* Clear all_ports_up flag if any link down */
> +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> +				all_ports_up = 0;
> +				break;
> +			}
> +		}
> +
> +		/* After finally printing all link rc, get out */
> +		if (print_flag == 1)
> +			break;
> +
> +		if (all_ports_up == 0) {
> +			printf(".");
> +			fflush(stdout);
> +			rte_delay_ms(CHECK_INTERVAL);
> +		}
> +
> +		/* Set the print_flag if all ports up or timeout */
> +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +			print_flag = 1;
> +			printf("Done\n");
> +		}
> +	}
> +}
> +
> +static bool
> +parser_usecases_read(char *usecases)
> +{
> +	bool valid = false;
> +	uint32_t i, j = 0;
> +	char *token;
> +
> +	token = strtok(usecases, ",");
> +	while (token != NULL) {
> +		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
> +			if (strcmp(supported_usecases[i], token) == 0) {
> +				graph_config.usecases[j].enabled = true;
> +				strcpy(graph_config.usecases[j].name, token);
> +				valid = true;
> +				j++;
> +				break;
> +			}
> +		}
> +		token = strtok(NULL, ",");
> +	}
> +
> +	return valid;
> +}
> +
> +static uint64_t
> +graph_worker_count_get(void)
> +{
> +	uint64_t nb_worker = 0;
> +	uint64_t coremask;
> +
> +	coremask = graph_config.params.coremask;
> +	while (coremask) {
> +		if (coremask & 0x1)
> +			nb_worker++;
> +
> +		coremask = (coremask >> 1);
> +	}
> +
> +	return nb_worker;
> +}
> +
> +static struct rte_node_ethdev_config *
> +graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
> +{
> +	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
> +	uint16_t queueid, portid, nb_graphs = 0;
> +	uint8_t nb_rx_queue, queue;
> +	struct lcore_conf *qconf;
> +
> +	n_tx_queue = graph_worker_count_get();
> +	if (n_tx_queue > RTE_MAX_ETHPORTS)
> +		n_tx_queue = RTE_MAX_ETHPORTS;
> +
> +	RTE_ETH_FOREACH_DEV(portid) {
> +		/* Skip ports that are not enabled */
> +		if ((enabled_port_mask & (1 << portid)) == 0) {
> +			printf("\nSkipping disabled port %d\n", portid);
> +			continue;
> +		}
> +
> +		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
> +
> +		/* Setup ethdev node config */
> +		ethdev_conf[nb_conf].port_id = portid;
> +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
> +		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
> +		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
> +
> +		nb_conf++;
> +	}
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +		fflush(stdout);
> +
> +		/* Init RX queues */
> +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +			portid = qconf->rx_queue_list[queue].port_id;
> +			queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +			/* Add this queue node to its graph */
> +			snprintf(qconf->rx_queue_list[queue].node_name,
> RTE_NODE_NAMESIZE,
> +				 "ethdev_rx-%u-%u", portid, queueid);
> +		}
> +		if (qconf->n_rx_queue)
> +			nb_graphs++;
> +	}
> +
> +	printf("\n");
> +
> +	ethdev_start();
> +	check_all_ports_link_status(enabled_port_mask);
> +
> +	*num_conf = nb_conf;
> +	*num_graphs = nb_graphs;
> +	return ethdev_conf;
> +}
> +
> +static int
> +graph_start(void)
> +{
> +	struct rte_node_ethdev_config *conf;
> +	uint32_t nb_graphs = 0, nb_conf, i;
> +
> +	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
> +	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
> +		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
> +			if (graph_config.usecases[i].enabled) {
> +				usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
> +				break;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int
> +graph_config_add(char *usecases, struct graph_config *config)
> +{
> +	if (!parser_usecases_read(usecases))
> +		return -EINVAL;
> +
> +	graph_config.params.bsz = config->params.bsz;
> +	graph_config.params.tmo = config->params.tmo;
> +	graph_config.params.coremask = config->params.coremask;
> +	graph_config.model = config->model;
> +
> +	return 0;
> +}
> +
> +int
> +graph_walk_start(void *conf)
> +{
> +	struct lcore_conf *qconf;
> +	struct rte_graph *graph;
> +	uint32_t lcore_id;
> +
> +	RTE_SET_USED(conf);
> +
> +	lcore_id = rte_lcore_id();
> +	qconf = &lcore_conf[lcore_id];
> +	graph = qconf->graph;
> +
> +	if (!graph) {
> +		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
> +		return 0;
> +	}
> +
> +	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n",
> lcore_id,
> +		qconf->name, graph);
> +
> +	while (likely(!force_quit))
> +		rte_graph_walk(graph);
> +
> +	return 0;
> +}
> +
> +void
> +graph_stats_print(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 = "worker_*";
> +
> +	/* 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
> +graph_config_process(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +		     void *obj __rte_unused)
> +{
> +	uint32_t bsz = 32, tmo = 0, coremask = 0xf;
> +	struct graph_config config;
> +	int idx = 2, rc;
> +	uint8_t model;
> +
> +	if (n_tokens < 4) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		return;
> +	}
> +
> +next_arg:
> +	if (strcmp(tokens[idx], "model")) {
> +		if (strcmp(tokens[idx], "bsz") == 0) {
> +			if (parser_uint32_read(&bsz, tokens[idx + 1])) {
> +				snprintf(out, out_size, MSG_ARG_INVALID, "bsz");
> +				return;
> +			}
> +
> +		} else if (strcmp(tokens[idx], "tmo") == 0) {
> +			if (parser_uint32_read(&tmo, tokens[idx + 1])) {
> +				snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> +				return;
> +			}
> +		} else if (strcmp(tokens[idx], "coremask") == 0) {
> +			coremask = strtol(tokens[idx + 1], NULL, 16);
> +			if (coremask == 0) {
> +				snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> +				return;
> +			}
> +		} else {
> +			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "usecases
> params");
> +			return;
> +		}
> +
> +		idx += 2;
> +		goto next_arg;
> +	} else {
> +		if (strcmp(tokens[idx + 1], "default") == 0) {
> +			model = GRAPH_MODEL_RTC;
> +		} else if (strcmp(tokens[idx + 1], "rtc") == 0) {
> +			model = GRAPH_MODEL_RTC;
> +		} else if (strcmp(tokens[idx + 1], "mcd") == 0) {
> +			model = GRAPH_MODEL_MCD;
> +		} else {
> +			snprintf(out, out_size, MSG_ARG_NOT_FOUND, "model
> arguments");
> +			return;
> +		}
> +	}
> +
> +	config.params.bsz = bsz;
> +	config.params.tmo = tmo;
> +	config.params.coremask = coremask;
> +	config.model = model;
> +	rc = graph_config_add(tokens[1], &config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +}
> +
> +static int
> +cli_graph_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> +	       size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "----------------------------- graph command help -----------------------------");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_graph_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", "graph start");
> +	return 0;
> +}
> +
> +static int
> +cli_graph(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	if (strcmp(tokens[1], "start") == 0)
> +		graph_start();
> +	else
> +		graph_config_process(tokens, n_tokens, out, out_size, obj);
> +
> +	return 0;
> +}
> +
> +static struct cli_module graph = {
> +	.cmd = "graph",
> +	.process = cli_graph,
> +	.usage = cli_graph_help,
> +};
> +
> +CLI_REGISTER(graph);
> diff --git a/app/graph/graph.h b/app/graph/graph.h
> new file mode 100644
> index 0000000000..126e967d75
> --- /dev/null
> +++ b/app/graph/graph.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_H
> +#define APP_GRAPH_H
> +
> +int graph_walk_start(void *conf);
> +void graph_stats_print(void);
> +
> +#endif
> diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
> new file mode 100644
> index 0000000000..655a028fb2
> --- /dev/null
> +++ b/app/graph/graph_priv.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_PRIV_H
> +#define APP_GRAPH_PRIV_H
> +
> +#define MAX_GRAPH_USECASES 32
> +
> +enum graph_model {
> +	GRAPH_MODEL_RTC = 0x01,
> +	GRAPH_MODEL_MCD = 0x02,
> +};
> +
> +struct usecases {
> +	char name[32];
> +	bool enabled;
> +};
> +
> +struct usecase_params {
> +	uint64_t coremask;
> +	uint32_t bsz;
> +	uint32_t tmo;
> +};
> +
> +struct graph_config {
> +	struct usecases usecases[MAX_GRAPH_USECASES];
> +	struct usecase_params params;
> +	enum graph_model model;
> +};
> +
> +#endif
> diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
> new file mode 100644
> index 0000000000..5aba5b38f2
> --- /dev/null
> +++ b/app/graph/ip4_route.c
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
> +
> +struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_netmask_to_depth(uint32_t netmask)
> +{
> +	uint8_t zerobits = 0;
> +
> +	while ((netmask & 0x1) == 0) {
> +		netmask = netmask >> 1;
> +		zerobits++;
> +	}
> +
> +	return (32 - zerobits);
> +}
> +
> +static int
> +route_ip4_add(struct ipv4_route_config *route)
> +{
> +	int i;
> +
> +	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +		if (!route4[i].is_used)
> +			break;
> +	}
> +
> +	if (i == MAX_ROUTE_ENTRIES)
> +		return -ENOMEM;

[Nithin] Change neigh and route database to dynamic linked list instead of using static array.

> +
> +	route4[i].ip = route->ip;
> +	route4[i].netmask = route->netmask;
> +	route4[i].via = route->via;
> +	route4[i].is_used = true;
> +	return 0;
> +}
> +
> +int
> +route_ip4_add_to_lookup(void)
> +{
> +	struct ipv4_route_config *route = NULL;
> +	int rc = -EINVAL;
> +	uint8_t depth;
> +	int portid, i;
> +
> +	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +		if (route4[i].is_used)
> +			route = &route4[i];
> +
> +		portid = ethdev_portid_by_ip4(route->via);
> +		if (portid < 0) {
> +			printf("Invalid portid found to install the route\n");
> +			return rc;
> +		}
> +
> +		depth = convert_netmask_to_depth(route->netmask);
> +
> +		rc = rte_node_ip4_route_add(route->ip, depth, portid,
> +					     RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
> +		if (rc < 0)
> +			return rc;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused,
> char *out,
> +		     size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "--------------------------- ipv4_lookup command help --------------------------
> ");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ipv4_lookup_help);
> +	return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +		void *obj __rte_unused)
> +{
> +	struct ipv4_route_config config;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 9) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&config.ip, tokens[4])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ipv4");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[5], "netmask")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&config.netmask, tokens[6])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[7], "via")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&config.via, tokens[8])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "via ip");
> +		goto exit;
> +	}
> +
> +	rc = route_ip4_add(&config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static struct cli_module ipv4_lookup = {
> +	.cmd = "ipv4_lookup",
> +	.process = cli_ipv4_lookup,
> +	.usage = cli_ipv4_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv4_lookup);
> diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
> new file mode 100644
> index 0000000000..2c5397f9d3
> --- /dev/null
> +++ b/app/graph/ip6_route.c
> @@ -0,0 +1,154 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip6_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
> +
> +struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_ip6_netmask_to_depth(uint8_t *netmask)
> +{
> +	uint8_t setbits = 0;
> +	uint8_t mask;
> +	int i;
> +
> +	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +		mask = netmask[i];
> +		while (mask & 0x80) {
> +			mask = mask << 1;
> +			setbits++;
> +		}
> +	}
> +
> +	return setbits;
> +}
> +
> +static int
> +route_ip6_add(struct ipv6_route_config *route)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> +		if (!route6[i].is_used)
> +			break;
> +	}
> +
> +	if (i == RTE_MAX_ETHPORTS)
> +		return -ENOMEM;
> +
> +	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +		route6[i].ip[j] = route->ip[j];
> +		route6[i].mask[j] = route->mask[j];
> +		route6[i].gateway[j] = route->gateway[j];
> +	}
> +	route6[i].is_used = true;
> +
> +	return 0;
> +}
> +
> +int
> +route_ip6_add_to_lookup(void)
> +{
> +	struct ipv6_route_config *route = NULL;
> +	int rc = -EINVAL;
> +	uint8_t depth;
> +	int portid, i;
> +
> +	for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> +		if (route6[i].is_used)
> +			route = &route6[i];
> +
> +		portid = ethdev_portid_by_ip6(route->gateway);
> +		if (portid < 0) {
> +			printf("Invalid portid found to install the route\n");
> +			return rc;
> +		}
> +
> +		depth = convert_ip6_netmask_to_depth(route->mask);
> +
> +		rc = rte_node_ip6_route_add(route->ip, depth, portid,
> +					     RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
> +		if (rc < 0)
> +			return rc;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused,
> char *out,
> +		     size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "--------------------------- ipv6_lookup command help --------------------------
> ");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_ipv6_lookup_help);
> +	return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> +		void *obj __rte_unused)
> +{
> +	struct ipv6_route_config config;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 9) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(config.ip, tokens[4])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ipv6");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[5], "netmask")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(config.mask, tokens[6])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[7], "via")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(config.gateway, tokens[8])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "gateway ip");
> +		goto exit;
> +	}
> +
> +	rc = route_ip6_add(&config);
> +	if (rc)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static struct cli_module ipv6_lookup = {
> +	.cmd = "ipv6_lookup",
> +	.process = cli_ipv6_lookup,
> +	.usage = cli_ipv6_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv6_lookup);
> diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
> new file mode 100644
> index 0000000000..85b8b2618e
> --- /dev/null
> +++ b/app/graph/l3fwd.c
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_lcore.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "module_api.h"
> +
> +static char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
> +static uint64_t packet_to_capture;
> +static int pcap_trace_enable;
> +
> +static int
> +l3fwd_pattern_configure(void)
> +{
> +	/* Graph initialization. 8< */
> +	static const char * const default_patterns[] = {
> +		"ip4*",
> +		"ethdev_tx-*",
> +		"pkt_drop",
> +	};
> +
> +	struct rte_graph_param graph_conf;
> +	const char **node_patterns;
> +	struct lcore_conf *qconf;
> +	uint16_t nb_patterns;
> +	uint8_t lcore_id;
> +	int rc;
> +
> +	nb_patterns = RTE_DIM(default_patterns);
> +	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
> +			sizeof(*node_patterns));
> +	if (!node_patterns)
> +		return -ENOMEM;
> +	memcpy(node_patterns, default_patterns,
> +			nb_patterns * sizeof(*node_patterns));
> +
> +	memset(&graph_conf, 0, sizeof(graph_conf));
> +	graph_conf.node_patterns = node_patterns;
> +
> +	/* Pcap config */
> +	graph_conf.pcap_enable = pcap_trace_enable;
> +	graph_conf.num_pkt_to_capture = packet_to_capture;
> +	graph_conf.pcap_filename = pcap_filename;
> +
> +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +		rte_graph_t graph_id;
> +		rte_edge_t i;
> +
> +		if (rte_lcore_is_enabled(lcore_id) == 0)
> +			continue;
> +
> +		qconf = &lcore_conf[lcore_id];
> +
> +		/* Skip graph creation if no source exists */
> +		if (!qconf->n_rx_queue)
> +			continue;
> +
> +		/* Add rx node patterns of this lcore */
> +		for (i = 0; i < qconf->n_rx_queue; i++) {
> +			graph_conf.node_patterns[nb_patterns + i] =
> +				qconf->rx_queue_list[i].node_name;
> +		}
> +
> +		graph_conf.nb_node_patterns = nb_patterns + i;
> +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> +				lcore_id);
> +
> +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> +		if (graph_id == RTE_GRAPH_ID_INVALID)
> +			rte_exit(EXIT_FAILURE,
> +					"rte_graph_create(): graph_id invalid"
> +					" for lcore %u\n", lcore_id);
> +
> +		qconf->graph_id = graph_id;
> +		qconf->graph = rte_graph_lookup(qconf->name);
> +		/* >8 End of graph initialization. */
> +		if (!qconf->graph)
> +			rte_exit(EXIT_FAILURE,
> +					"rte_graph_lookup(): graph %s not found\n",
> +					qconf->name);
> +	}
> +
> +	rc = route_ip4_add_to_lookup();
> +	if (rc < 0)
> +		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
> +
> +	rc = route_ip6_add_to_lookup();
> +	if (rc < 0)
> +		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
> +
> +	rc = neigh_ip4_add_to_rewrite();
> +	if (rc < 0)
> +		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
> +
> +	rc = neigh_ip6_add_to_rewrite();
> +	if (rc < 0)
> +		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
> +
> +	/* Launch per-lcore init on every worker lcore */
> +	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
> +
> +	/* Accumulate and print stats on main until exit */
> +	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
> +		graph_stats_print();
> +
> +	/* Wait for worker cores to exit */
> +	rc = 0;
> +	RTE_LCORE_FOREACH_WORKER(lcore_id) {
> +		rc = rte_eal_wait_lcore(lcore_id);
> +		/* Destroy graph */
> +		if (rc < 0 ||
> +		    rte_graph_destroy(rte_graph_from_name(lcore_conf[lcore_id].name)))
> {
> +			rc = -1;
> +			break;
> +		}
> +	}
> +	free(node_patterns);
> +
> +	ethdev_stop();
> +	return rc;
> +}
> +
> +int
> +usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs,
> uint16_t nb_graphs)
> +{
> +	int rc;
> +
> +	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
> +	if (rc)
> +		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
> +
> +	rc = l3fwd_pattern_configure();
> +	if (rc)
> +		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
> +
> +	return rc;
> +}
> diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
> new file mode 100644
> index 0000000000..e1d23165e6
> --- /dev/null
> +++ b/app/graph/l3fwd.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_L3FWD_H
> +#define APP_GRAPH_L3FWD_H
> +
> +int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
> +			    uint16_t nb_graphs);
> +
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> new file mode 100644
> index 0000000000..e9934025bf
> --- /dev/null
> +++ b/app/graph/main.c
> @@ -0,0 +1,201 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_eal.h>
> +#include <rte_launch.h>
> +
> +#include "module_api.h"
> +
> +volatile bool force_quit;
> +
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-
> stats] "
> +			    "[--help]\n";
> +
> +static struct app_params {
> +	struct conn_params conn;
> +	char *script_name;
> +	bool enable_graph_stats;
> +} app = {
> +	.conn = {
> +		.welcome = "\nWelcome!\n\n",
> +		.prompt = "graph> ",
> +		.addr = "0.0.0.0",
> +		.port = 8086,
> +		.buf_size = 1024 * 1024,
> +		.msg_in_len_max = 1024,
> +		.msg_out_len_max = 1024 * 1024,
> +		.msg_handle = cli_process,
> +		.msg_handle_arg = NULL, /* set later. */
> +	},
> +	.script_name = NULL,
> +	.enable_graph_stats = false,
> +};
> +
> +static void
> +signal_handler(int signum)
> +{
> +	if (signum == SIGINT || signum == SIGTERM) {
> +		printf("\n\nSignal %d received, preparing to exit...\n", signum);
> +		force_quit = true;
> +	}
> +}
> +
> +static int
> +app_args_parse(int argc, char **argv)
> +{
> +	struct option lgopts[] = {
> +		{"help", 0, 0, 'H'},
> +		{"enable-graph-stats", 0, 0, 'g'},
> +	};
> +	int h_present, p_present, s_present, n_args, i;
> +	char *app_name = argv[0];
> +	int opt, option_index;
> +
> +	/* Skip EAL input args */
> +	n_args = argc;
> +	for (i = 0; i < n_args; i++)
> +		if (strcmp(argv[i], "--") == 0) {
> +			argc -= i;
> +			argv += i;
> +			break;
> +		}
> +
> +	if (i == n_args)
> +		return 0;
> +
> +	/* Parse args */
> +	h_present = 0;
> +	p_present = 0;
> +	s_present = 0;
> +
> +	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
> +		switch (opt) {
> +		case 'h':
> +			if (h_present) {
> +				printf("Error: Multiple -h arguments\n");
> +				return -1;
> +			}
> +			h_present = 1;
> +
> +			if (!strlen(optarg)) {
> +				printf("Error: Argument for -h not provided\n");
> +				return -1;
> +			}
> +
> +			app.conn.addr = strdup(optarg);
> +			if (app.conn.addr == NULL) {
> +				printf("Error: Not enough memory\n");
> +				return -1;
> +			}
> +			break;
> +
> +		case 'p':
> +			if (p_present) {
> +				printf("Error: Multiple -p arguments\n");
> +				return -1;
> +			}
> +			p_present = 1;
> +
> +			if (!strlen(optarg)) {
> +				printf("Error: Argument for -p not provided\n");
> +				return -1;
> +			}
> +
> +			app.conn.port = (uint16_t) atoi(optarg);
> +			break;
> +
> +		case 's':
> +			if (s_present) {
> +				printf("Error: Multiple -s arguments\n");
> +				return -1;
> +			}
> +			s_present = 1;
> +
> +			if (!strlen(optarg)) {
> +				printf("Error: Argument for -s not provided\n");
> +				return -1;
> +			}
> +
> +			app.script_name = strdup(optarg);
> +			if (app.script_name == NULL) {
> +				printf("Error: Not enough memory\n");
> +				return -1;
> +			}
> +			break;
> +
> +		case 'g':
> +			app.enable_graph_stats = true;
> +			break;
> +
> +		case 'H':
> +		default:
> +			printf(usage, app_name);
> +			return -1;
> +		}
> +	}
> +	optind = 1; /* reset getopt lib */
> +
> +	return 0;
> +}
> +
> +bool
> +app_graph_stats_enabled(void)
> +{
> +	return app.enable_graph_stats;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +	struct conn *conn;
> +	int rc;
> +
> +	/* Parse application arguments */
> +	rc = app_args_parse(argc, argv);
> +	if (rc < 0)
> +		return rc;
> +
> +	/* EAL */
> +	rc = rte_eal_init(argc, argv);
> +	if (rc < 0) {
> +		printf("Error: EAL initialization failed (%d)\n", rc);
> +		return rc;
> +	};
> +
> +	force_quit = false;
> +	signal(SIGINT, signal_handler);
> +	signal(SIGTERM, signal_handler);
> +
> +	/* Script */
> +	if (app.script_name) {
> +		cli_script_process(app.script_name, app.conn.msg_in_len_max,
> +			app.conn.msg_out_len_max, NULL);
> +	}
> +
> +	/* Connectivity */
> +	app.conn.msg_handle_arg = NULL;
> +	conn = conn_init(&app.conn);
> +	if (!conn) {
> +		printf("Error: Connectivity initialization failed (%d)\n", rc);
> +		return rc;
> +	};
> +
> +	/* Dispatch loop */
> +	while (1) {
> +		conn_req_poll(conn);
> +
> +		conn_msg_poll(conn);
> +	}
> +
> +	/* clean up the EAL */
> +	rte_eal_cleanup();
> +}
> diff --git a/app/graph/mempool.c b/app/graph/mempool.c
> new file mode 100644
> index 0000000000..1cee66abed
> --- /dev/null
> +++ b/app/graph/mempool.c
> @@ -0,0 +1,134 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_mbuf.h>
> +
> +#include "mempool_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers
> <number_of_buffers> "
> +		     "cache <cache_size> numa <numa_id>";
> +
> +struct mempools mpconfig;
> +
> +int
> +mempool_process(struct mempool_config *config)
> +{
> +	struct rte_mempool *mp;
> +	uint8_t nb_pools;
> +
> +	nb_pools = mpconfig.nb_pools;
> +	strcpy(mpconfig.config[nb_pools].name, config->name);
> +	mpconfig.config[nb_pools].pool_size = config->pool_size;
> +	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
> +	mpconfig.config[nb_pools].cache_size = config->cache_size;
> +	mpconfig.config[nb_pools].numa_node = config->numa_node;
> +
> +	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config-
> >cache_size,
> +		64, config->buffer_size, config->numa_node);
> +	if (!mp)
> +		return -EINVAL;
> +
> +	mpconfig.mp[nb_pools] = mp;
> +	nb_pools++;
> +	mpconfig.nb_pools = nb_pools;
> +
> +	return 0;
> +}
> +
> +static int
> +cli_mempool_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> +		 size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "---------------------------- mempool command help ----------------------------");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_mempool_help);
> +	return 0;
> +}
> +
> +static int
> +cli_mempool(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	uint32_t pkt_buffer_size, pool_size, cache_size, numa_node;
> +	struct mempool_config config;
> +	int rc = -EINVAL;
> +
> +	if (n_tokens != 10) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[2], "size")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "size");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&pkt_buffer_size, tokens[3])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "mbuf_size");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[4], "buffers")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "buffers");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&pool_size, tokens[5])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "number_of_buffers");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[6], "cache")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "cache");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&cache_size, tokens[7])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "cache_size");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[8], "numa")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "numa");
> +		goto exit;
> +	}
> +
> +	if (parser_uint32_read(&numa_node, tokens[9])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "numa_id");
> +		goto exit;
> +	}
> +
> +	strcpy(config.name, tokens[1]);
> +	config.name[strlen(tokens[1])] = '\0';
> +	config.pool_size = pool_size;
> +	config.buffer_size = pkt_buffer_size;
> +	config.cache_size = cache_size;
> +	config.numa_node = numa_node;
> +
> +	rc = mempool_process(&config);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static struct cli_module mempool = {
> +	.cmd = "mempool",
> +	.process = cli_mempool,
> +	.usage = cli_mempool_help,
> +};
> +
> +CLI_REGISTER(mempool);
> diff --git a/app/graph/mempool.h b/app/graph/mempool.h
> new file mode 100644
> index 0000000000..5fc788199d
> --- /dev/null
> +++ b/app/graph/mempool.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_H
> +#define APP_GRAPH_MEMPOOL_H
> +
> +struct mempool_config {
> +	char name[RTE_MEMPOOL_NAMESIZE];
> +	int pool_size;
> +	int cache_size;
> +	int buffer_size;
> +	int numa_node;
> +};
> +
> +int mempool_process(struct mempool_config *config);
> +
> +#endif
> diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
> new file mode 100644
> index 0000000000..5a55722b32
> --- /dev/null
> +++ b/app/graph/mempool_priv.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_PRIV_H
> +#define APP_GRAPH_MEMPOOL_PRIV_H
> +
> +#include "mempool.h"
> +
> +struct mempools {
> +	struct mempool_config config[RTE_MAX_ETHPORTS];
> +	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
> +	uint8_t	nb_pools;
> +};
> +
> +#endif
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> new file mode 100644
> index 0000000000..a3011e504b
> --- /dev/null
> +++ b/app/graph/meson.build
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 Marvell.
> +
> +# override default name to drop the hyphen
> +name = 'graph'
> +build = cc.has_header('sys/epoll.h')
> +if not build
> +    subdir_done()
> +endif
> +
> +deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node']
> +sources = files(
> +        'cli.c',
> +        'conn.c',
> +        'ethdev_rx.c',
> +        'ethdev.c',
> +        'graph.c',
> +        'ip4_route.c',
> +        'ip6_route.c',
> +        'main.c',
> +        'mempool.c',
> +        'neigh.c',
> +        'l3fwd.c',
> +        'utils.c',
> +)
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> new file mode 100644
> index 0000000000..09b10bc672
> --- /dev/null
> +++ b/app/graph/module_api.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MODULE_API_H
> +#define APP_GRAPH_MODULE_API_H
> +
> +#include <stdint.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "conn.h"
> +#include "cli.h"
> +#include "ethdev.h"
> +#include "ethdev_rx.h"
> +#include "graph.h"
> +#include "l3fwd.h"
> +#include "mempool.h"
> +#include "neigh.h"
> +#include "route.h"
> +#include "utils.h"
> +
> +/*
> + * Externs
> + */
> +extern volatile bool force_quit;
> +
> +bool app_graph_stats_enabled(void);
> +
> +#endif
> diff --git a/app/graph/neigh.c b/app/graph/neigh.c
> new file mode 100644
> index 0000000000..07766758c9
> --- /dev/null
> +++ b/app/graph/neigh.c
> @@ -0,0 +1,269 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "neigh_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
> +
> +static const char
> +cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
> +
> +struct ipv4_neigh_config neigh4[MAX_NEIGH_ENTRIES];
> +struct ipv6_neigh_config neigh6[MAX_NEIGH_ENTRIES];
> +
> +static int
> +neigh_ip4_add(uint32_t ip, uint64_t mac)
> +{
> +	int i;
> +
> +	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +		if (!neigh4[i].is_used)
> +			break;
> +	}
> +
> +	if (i == MAX_NEIGH_ENTRIES)
> +		return -ENOMEM;
> +
> +	neigh4[i].ip = ip;
> +	neigh4[i].mac = mac;
> +	neigh4[i].is_used = true;
> +	return 0;
> +}
> +
> +static int
> +neigh_ip6_add(uint8_t *ip, uint64_t mac)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +		if (!neigh6[i].is_used)
> +			break;
> +	}
> +
> +	if (i == MAX_NEIGH_ENTRIES)
> +		return -ENOMEM;
> +
> +	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
> +		neigh6[i].ip[j] = ip[j];
> +
> +	neigh6[i].mac = mac;
> +	neigh6[i].is_used = true;
> +	return 0;
> +}
> +
> +int
> +neigh_ip4_add_to_rewrite(void)
> +{
> +	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +	struct rte_ether_addr smac = {0};
> +	struct ipv4_neigh_config *neigh;
> +	int16_t portid = 0;
> +	int rc, i;
> +
> +	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +		if (!neigh4[i].is_used)
> +			continue;
> +
> +		neigh = &neigh4[i];
> +		portid = ethdev_portid_by_ip4(neigh->ip);
> +		if (portid < 0) {
> +			printf("Invalid portid found to add  neigh\n");
> +			return -EINVAL;
> +		}
> +
> +		memset(data, 0, len);
> +
> +		/* Copy dst mac */
> +		rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> +		/* Copy src mac */
> +		rc = rte_eth_macaddr_get(portid, &smac);
> +		if (rc < 0) {
> +			printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
> +			return rc;
> +		}
> +
> +		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> +		rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
> +		if (rc < 0) {
> +			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
> +			return rc;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +neigh_ip6_add_to_rewrite(void)
> +{
> +	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +	struct rte_ether_addr smac = {0};
> +	struct ipv6_neigh_config *neigh;
> +	int16_t portid = 0;
> +	int rc, i;
> +
> +	for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> +		if (!neigh6[i].is_used)
> +			continue;
> +
> +		neigh = &neigh6[i];
> +		portid = ethdev_portid_by_ip6(neigh->ip);
> +		if (portid < 0) {
> +			printf("Invalid portid found to add neigh\n");
> +			return -EINVAL;
> +		}
> +
> +		memset(data, 0, len);
> +
> +		/* Copy dst mac */
> +		rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> +		/* Copy src mac */
> +		rc = rte_eth_macaddr_get(portid, &smac);
> +		if (rc < 0) {
> +			printf("Cannot get MAC address: err=%d, port=%d\n",
> +				rc, portid);
> +			return rc;
> +		}
> +
> +		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> +		rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
> +		if (rc < 0) {
> +			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
> +			return rc;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +cmd_neigh_v4(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	int rc = -EINVAL;
> +	uint64_t mac;
> +	uint32_t ip;
> +
> +	if (n_tokens != 5) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[1], "add")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[2], "ipv4")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv4");
> +		goto exit;
> +	}
> +
> +	if (parser_ip4_read(&ip, tokens[3])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +		goto exit;
> +	}
> +
> +	if (parser_mac_read(&mac, tokens[4])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> +		goto exit;
> +	}
> +
> +	rc = neigh_ip4_add(ip, mac);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static int
> +cmd_neigh_v6(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> +	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +	int rc = -EINVAL;
> +	uint64_t mac;
> +
> +	if (n_tokens != 5) {
> +		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[1], "add")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> +		goto exit;
> +	}
> +
> +	if (strcmp(tokens[2], "ipv6")) {
> +		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv6");
> +		goto exit;
> +	}
> +
> +	if (parser_ip6_read(ip, tokens[3])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> +		goto exit;
> +	}
> +
> +	if (parser_mac_read(&mac, tokens[4])) {
> +		snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> +		goto exit;
> +	}
> +
> +	rc = neigh_ip6_add(ip, mac);
> +	if (rc < 0)
> +		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> +	return rc;
> +}
> +
> +static int
> +cli_neigh_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
> +		     size_t out_size, void *obj __rte_unused)
> +{
> +	size_t len;
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "\n%s\n",
> +		 "----------------------------- neigh command help -----------------------------");
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_neigh_v4_help);
> +
> +	len = strlen(out);
> +	snprintf(out + len, out_size, "%s\n", cmd_neigh_v6_help);
> +	return 0;
> +}
> +
> +static int
> +cli_neigh(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
> +{
> +	if (strcmp(tokens[2], "ipv4") == 0)
> +		return cmd_neigh_v4(tokens, n_tokens, out, out_size, obj);
> +	else
> +		return cmd_neigh_v6(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module neigh = {
> +	.cmd = "neigh",
> +	.process = cli_neigh,
> +	.usage = cli_neigh_help,
> +};
> +
> +CLI_REGISTER(neigh);

[Nithin] Move logic for tokenizing into cmdline library itself. Module should only register the commands to libcmdline.

> diff --git a/app/graph/neigh.h b/app/graph/neigh.h
> new file mode 100644
> index 0000000000..3964c37bb0
> --- /dev/null
> +++ b/app/graph/neigh.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_H
> +#define APP_GRAPH_NEIGH_H
> +
> +int neigh_ip4_add_to_rewrite(void);
> +int neigh_ip6_add_to_rewrite(void);
> +
> +#endif
> diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
> new file mode 100644
> index 0000000000..745dc7d671
> --- /dev/null
> +++ b/app/graph/neigh_priv.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_PRIV_H
> +#define APP_GRAPH_NEIGH_PRIV_H
> +
> +#define MAX_NEIGH_ENTRIES 32
> +
> +struct ipv4_neigh_config {
> +	uint32_t ip;
> +	uint64_t mac;
> +	bool is_used;
> +};
> +
> +struct ipv6_neigh_config {
> +	uint8_t ip[16];
> +	uint64_t mac;
> +	bool is_used;
> +};
> +
> +#endif
> diff --git a/app/graph/route.h b/app/graph/route.h
> new file mode 100644
> index 0000000000..6b4acf3344
> --- /dev/null
> +++ b/app/graph/route.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ROUTE_H
> +#define APP_GRAPH_ROUTE_H
> +
> +#define MAX_ROUTE_ENTRIES 32
> +
> +struct ipv4_route_config {
> +	uint32_t ip;
> +	uint32_t netmask;
> +	uint32_t via;
> +	bool is_used;
> +};
> +
> +struct ipv6_route_config {
> +	uint8_t ip[16];
> +	uint8_t mask[16];
> +	uint8_t gateway[16];
> +	bool is_used;
> +};
> +
> +extern struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +extern struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +int route_ip4_add_to_lookup(void);
> +int route_ip6_add_to_lookup(void);
> +
> +#endif
> diff --git a/app/graph/utils.c b/app/graph/utils.c
> new file mode 100644
> index 0000000000..48a83e738c
> --- /dev/null
> +++ b/app/graph/utils.c
> @@ -0,0 +1,155 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +
> +#include "module_api.h"
> +
> +#define white_spaces_skip(pos)			\
> +({						\
> +	__typeof__(pos) _p = (pos);		\
> +	for ( ; isspace(*_p); _p++)		\
> +		;				\
> +	_p;					\
> +})
> +
> +static void
> +hex_string_to_uint64(uint64_t *dst, const char *hexs)
> +{
> +	char buf[2] = {0};
> +	uint8_t shift = 4;
> +	int iter = 0;
> +	char c;
> +
> +	while ((c = *hexs++)) {
> +		buf[0] = c;
> +		*dst |= (strtol(buf, NULL, 16) << shift);
> +		shift -= 4;
> +		iter++;
> +		if (iter == 2) {
> +			iter = 0;
> +			shift = 4;
> +			dst++;
> +		}
> +	}
> +}
> +
> +int
> +parser_uint64_read(uint64_t *value, const char *p)
> +{
> +	char *next;
> +	uint64_t val;
> +
> +	p = white_spaces_skip(p);
> +	if (!isdigit(*p))
> +		return -EINVAL;
> +
> +	val = strtoul(p, &next, 0);
> +	if (p == next)
> +		return -EINVAL;
> +
> +	p = next;
> +	switch (*p) {
> +	case 'T':
> +		val *= 1024ULL;
> +		/* fall through */
> +	case 'G':
> +		val *= 1024ULL;
> +		/* fall through */
> +	case 'M':
> +		val *= 1024ULL;
> +		/* fall through */
> +	case 'k':
> +	case 'K':
> +		val *= 1024ULL;
> +		p++;
> +		break;
> +	}
> +
> +	p = white_spaces_skip(p);
> +	if (*p != '\0')
> +		return -EINVAL;
> +
> +	*value = val;
> +	return 0;
> +}
> +
> +int
> +parser_uint32_read(uint32_t *value, const char *p)
> +{
> +	uint64_t val = 0;
> +	int rc = parser_uint64_read(&val, p);
> +
> +	if (rc < 0)
> +		return rc;
> +
> +	if (val > UINT32_MAX)
> +		return -ERANGE;
> +
> +	*value = val;
> +	return 0;
> +}
> +
> +int
> +parser_ip4_read(uint32_t *value, char *p)
> +{
> +	uint8_t shift = 24;
> +	uint32_t ip = 0;
> +	char *token;
> +
> +	token = strtok(p, ".");
> +	while (token != NULL) {
> +		ip |= (atoi(token) << shift);
> +		token = strtok(NULL, ".");
> +		shift -= 8;
> +	}
> +
> +	*value = ip;
> +
> +	return 0;
> +}
> +
> +int
> +parser_ip6_read(uint8_t *value, char *p)
> +{
> +	uint64_t val = 0;
> +	char *token;
> +
> +	token = strtok(p, ":");
> +	while (token != NULL) {
> +		hex_string_to_uint64(&val, token);
> +		*value = val;
> +		token = strtok(NULL, ":");
> +		value++;
> +		val = 0;
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +parser_mac_read(uint64_t *value, char *p)
> +{
> +	uint64_t mac = 0, val = 0;
> +	uint8_t shift = 40;
> +	char *token;
> +
> +	token = strtok(p, ":");
> +	while (token != NULL) {
> +		hex_string_to_uint64(&val, token);
> +		mac |= val << shift;
> +		token = strtok(NULL, ":");
> +		shift -= 8;
> +		val = 0;
> +	}
> +
> +	*value = mac;
> +
> +	return 0;
> +}
> diff --git a/app/graph/utils.h b/app/graph/utils.h
> new file mode 100644
> index 0000000000..0ebb5de55a
> --- /dev/null
> +++ b/app/graph/utils.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_UTILS_H
> +#define APP_GRAPH_UTILS_H
> +
> +int parser_uint64_read(uint64_t *value, const char *p);
> +int parser_uint32_read(uint32_t *value, const char *p);
> +int parser_ip4_read(uint32_t *value, char *p);
> +int parser_ip6_read(uint8_t *value, char *p);
> +int parser_mac_read(uint64_t *value, char *p);
> +
> +#endif
> diff --git a/app/meson.build b/app/meson.build
> index e4bf5c531c..728c936383 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -17,6 +17,7 @@ endif
>  apps = [
>          'dumpcap',
>          'pdump',
> +        'graph',
>          'proc-info',
>          'test-acl',
>          'test-bbdev',
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> new file mode 100644
> index 0000000000..c90dc9ad7f
> --- /dev/null
> +++ b/doc/guides/tools/graph.rst
> @@ -0,0 +1,171 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(c) 2023 Marvell.
> +
> +dpdk-graph Application
> +======================
> +
> +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> +application that allows exercising various graph use cases.
> +This application has a generic framework to add new graph based use cases to
> +verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
> +Based on the input file, application creates a graph to cater the use case.
> +
> +Supported Use cases
> +-------------------
> + * l3fwd
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options which can be provided in
> +following syntax
> +
> +.. code-block:: console
> +
> +   dpdk-graph [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +Following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
> +        list of cores to be used.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +Following are the application command-line options:
> +
> +* ``-h``
> +
> +        Set the host IPv4 address over which telnet session can be opened.
> +        It is an optional parameter. Default host address is 0.0.0.0.
> +
> +* ``-p``
> +
> +        Set the L4 port number over which telnet session can be opened.
> +	It is an optional parameter. Default port is 8086.
> +
> +* ``-s``
> +
> +        Script name with absolute path which specifies the use case. It is
> +        a mandatory parameter which will be used to create desired graph
> +        for a given use case.
> +
> +* ``--enable-graph-stats``
> +
> +       Enable graph statistics printing on console. By default graph statistics are disabled.
> +
> +* ``--help``
> +
> +       Dumps application usage
> +
> +Supported CLI commands
> +----------------------
> +
> +This section provides details on commands which can be used in ``<usecase>.cli``
> +file to express the requested use case configuration.
> +
> +.. list-table:: Exposed CLIs
> +   :header-rows: 1
> +   :widths: auto
> +
> +   * - Command
> +     - Description
> +     - Dynamic
> +     - Optional
> +   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd |
> default>
> +     - Command to express the desired use case
> +     - No
> +     - No
> +   * - graph start
> +     - Command to start the graph.
> +       This command triggers that no more commands are left to be parsed and graph
> +       initialization can be started now. It must be the last command in ``<usecase>.cli``
> +     - No
> +     - No
> +   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache
> <cache_size> numa <numa_id>
> +     - Command to create mempool which will be further associated to RxQ to dequeue the
> packets
> +     - No
> +     - No
> +   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name> [mtu
> <mtu_sz>]
> +     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
> +       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
> +       share the same mempool.
> +     - No
> +     - No
> +   * - ethdev <ethdev_name> mtu <mtu_sz>
> +     - Command to configure MTU of DPDK port
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> promiscuous <on/off>
> +     - Command to enable/disable promiscuous mode on DPDK port
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> show
> +     - Command to dump current ethdev configuration
> +     - Yes
> +     - Yes
> +   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> +     - Command to configure IPv4 address on given PCI device. It is needed if user
> +       wishes to use ``ipv4_lookup`` node
> +     - No
> +     - Yes
> +   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> +     - Command to configure IPv6 address on given PCI device. It is needed if user
> +       wishes to use ``ipv6_lookup`` node
> +     - No
> +     - Yes
> +   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
> +     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
> +       wishes to route the packets based on LPM lookup table.
> +     - No
> +     - Yes
> +   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
> +     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
> +       wishes to route the packets based on LPM6 lookup table.
> +     - No
> +     - Yes
> +   * - neigh add ipv4 <ip> <mac>
> +     - Command to add a neighbour information into ``ipv4_rewrite`` node.
> +     - No
> +     - Yes
> +   * - neigh add ipv6 <ip> <mac>
> +     - Command to add a neighbour information into ``ipv6_rewrite`` node.
> +     - No
> +     - Yes
> +   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
> +     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
> +       node instance will be pinned on given core and will poll on requested
> +       port/queue pair.
> +     - No
> +     - No
> +

[Nithin] Add CLI to get ethdev stats, graph stats via telnet

> +Runtime configuration
> +---------------------
> +
> +Application allows some configuration to be modified at runtime using a telnet session.
> +Application initiates a telnet server with host address 0.0.0.0 and port number 8086 if
> +``-h`` and ``-p`` is not given otherwise user provided IPv4 address and port number will
> +be used.
> +
> +After successful launch of application, client can connect to using host/port address and
> +console will be accessed with prompt ``graph>``.

[Nithin] Update Telnet session connection example with log

> +
> +Created graph for use case
> +--------------------------
> +
> +On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
> +This section mentions the created graph for each use case.
> +
> +l3fwd
> +~~~~~
> +
> +.. _figure_l3fwd_graph:
> +
> +.. figure:: img/graph-usecase-l3fwd.*
> diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-
> usecase-l3fwd.svg
> new file mode 100644
> index 0000000000..3b991c4cf0
> --- /dev/null
> +++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
> @@ -0,0 +1,210 @@
> +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
> + "https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_Graphics_SVG_1.1_DTD_svg11.dtd&d=DwIDAg&c=nKjWec2b6R0mOyPa
> z7xtfQ&r=FZ_tPCbgFOh18zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfAp
> ezlB2TtUrWk19QRB6M5nl_A9wsLObEPqlXYSqUau7ZfBV&s=QNoHRxZ4WaV84k-
> 6IlzN8d9bMQsg8Go9iFXzE0lgT-o&e= ">
> +<!-- Generated by graphviz version 2.43.0 (0)
> + -->
> +<!-- SPDX-License-Identifier: BSD-3-Clause -->
> +<!-- Copyright(C) 2023 Marvell. -->
> +<!--
> +
> +Generated with following command
> +dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
> +cat dot.dot
> +digraph dpdk_app_graph_l3fwd_nodes_flow {
> +    ingress_port [shape=rect]
> +    ethdev_rx
> +    pkt_cls
> +    ip4_lookup
> +    ip6_lookup
> +    ip4_rewrite
> +    ip6_rewrite
> +    ethdev_tx
> +    pkt_drop
> +    egress_port  [shape=rect]
> +
> +    ingress_port -> ethdev_rx [label="ingress packet"]
> +
> +    ethdev_rx -> pkt_cls
> +
> +    pkt_cls -> ip4_lookup [color="green"]
> +    pkt_cls -> ip6_lookup [color="blue"]
> +    pkt_cls -> pkt_drop   [color="red" style="dashed"]
> +
> +    ip4_lookup -> ip4_rewrite [color="green"]
> +    ip4_lookup -> pkt_drop [color="red" style="dashed"]
> +
> +    ip6_lookup -> ip6_rewrite [color="blue"]
> +    ip6_lookup -> pkt_drop [color="red" style="dashed"]
> +
> +    ip4_rewrite -> ethdev_tx [color="green"]
> +    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
> +
> +    ip6_rewrite -> ethdev_tx [color="blue"]
> +    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
> +
> +    ethdev_tx -> egress_port [label="egress packet"]
> +    ethdev_tx -> pkt_drop [color="red" style="dashed"]
> +}
> +
> + -->
> +<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
> +<svg width="550pt" height="510pt"
> + viewBox="0.00 0.00 549.50 510.00"
> xmlns="https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_2000_svg&d=DwIDAg&c=nKjWec2b6R0mOyPaz7xtfQ&r=FZ_tPCbgFOh1
> 8zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfApezlB2TtUrWk19QRB6M5nl
> _A9wsLObEPqlXYSqUau7ZfBV&s=KLiZ62_Z8HSL_a9Mq0OR-PCG_h1JavRUfbKsPOc4IAo&e=
> " xmlns:xlink="http://www.w3.org/1999/xlink">
> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
> +<title>dpdk_app_graph_l3fwd_nodes_flow</title>
> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
> +<!-- ingress_port -->
> +<g id="node1" class="node">
> +<title>ingress_port</title>
> +<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466
> 489.5,-502"/>
> +<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-
> size="14.00">ingress_port</text>
> +</g>
> +<!-- ethdev_rx -->
> +<g id="node2" class="node">
> +<title>ethdev_rx</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-
> size="14.00">ethdev_rx</text>
> +</g>
> +<!-- ingress_port&#45;&gt;ethdev_rx -->
> +<g id="edge1" class="edge">
> +<title>ingress_port&#45;&gt;ethdev_rx</title>
> +<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-
> 425.24"/>
> +<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-
> 425.18"/>
> +<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-
> size="14.00">ingress packet</text>
> +</g>
> +<!-- pkt_cls -->
> +<g id="node3" class="node">
> +<title>pkt_cls</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-
> size="14.00">pkt_cls</text>
> +</g>
> +<!-- ethdev_rx&#45;&gt;pkt_cls -->
> +<g id="edge2" class="edge">
> +<title>ethdev_rx&#45;&gt;pkt_cls</title>
> +<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-
> 352.07"/>
> +<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-
> 352.03"/>
> +</g>
> +<!-- ip4_lookup -->
> +<g id="node4" class="node">
> +<title>ip4_lookup</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-
> size="14.00">ip4_lookup</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;ip4_lookup -->
> +<g id="edge3" class="edge">
> +<title>pkt_cls&#45;&gt;ip4_lookup</title>
> +<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-
> 279.07"/>
> +<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-
> 279.03"/>
> +</g>
> +<!-- ip6_lookup -->
> +<g id="node5" class="node">
> +<title>ip6_lookup</title>
> +<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
> +<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-
> size="14.00">ip6_lookup</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;ip6_lookup -->
> +<g id="edge4" class="edge">
> +<title>pkt_cls&#45;&gt;ip6_lookup</title>
> +<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75
> 335.97,-271.65"/>
> +<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68
> 337.39,-268.45"/>
> +</g>
> +<!-- pkt_drop -->
> +<g id="node9" class="node">
> +<title>pkt_drop</title>
> +<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
> +<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-
> size="14.00">pkt_drop</text>
> +</g>
> +<!-- pkt_cls&#45;&gt;pkt_drop -->
> +<g id="edge5" class="edge">
> +<title>pkt_cls&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-
> 301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79
> 420.91,-25.78"/>
> +<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-
> 22.29"/>
> +</g>
> +<!-- ip4_rewrite -->
> +<g id="node6" class="node">
> +<title>ip4_rewrite</title>
> +<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
> +<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-
> size="14.00">ip4_rewrite</text>
> +</g>
> +<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
> +<g id="edge6" class="edge">
> +<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
> +<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29
> 409.79,-204.85"/>
> +<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59
> 412.78,-203.02"/>
> +</g>
> +<!-- ip4_lookup&#45;&gt;pkt_drop -->
> +<g id="edge7" class="edge">
> +<title>ip4_lookup&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-
> 222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41
> 412.64,-67.99 386.65,-42.17"/>
> +<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-
> 39.54"/>
> +</g>
> +<!-- ip6_rewrite -->
> +<g id="node7" class="node">
> +<title>ip6_rewrite</title>
> +<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
> +<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-
> size="14.00">ip6_rewrite</text>
> +</g>
> +<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
> +<g id="edge8" class="edge">
> +<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
> +<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31
> 238.52,-201.87"/>
> +<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33
> 240.43,-198.9"/>
> +</g>
> +<!-- ip6_lookup&#45;&gt;pkt_drop -->
> +<g id="edge9" class="edge">
> +<title>ip6_lookup&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-
> 214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
> +<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-
> 46.92"/>
> +</g>
> +<!-- ethdev_tx -->
> +<g id="node8" class="node">
> +<title>ethdev_tx</title>
> +<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
> +<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-
> size="14.00">ethdev_tx</text>
> +</g>
> +<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
> +<g id="edge10" class="edge">
> +<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
> +<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51
> 287.98,-124.84"/>
> +<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92
> 289.39,-121.63"/>
> +</g>
> +<!-- ip4_rewrite&#45;&gt;pkt_drop -->
> +<g id="edge11" class="edge">
> +<title>ip4_rewrite&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48
> 374.03,-78.99 367.22,-46.38"/>
> +<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-
> 45.26"/>
> +</g>
> +<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
> +<g id="edge12" class="edge">
> +<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
> +<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-
> 132.14"/>
> +<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28
> 238.31,-133.65"/>
> +</g>
> +<!-- ip6_rewrite&#45;&gt;pkt_drop -->
> +<g id="edge13" class="edge">
> +<title>ip6_rewrite&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-
> 140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
> +<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-
> 28.24"/>
> +</g>
> +<!-- ethdev_tx&#45;&gt;pkt_drop -->
> +<g id="edge15" class="edge">
> +<title>ethdev_tx&#45;&gt;pkt_drop</title>
> +<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85
> 313.31,-55.57 332.84,-40.75"/>
> +<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-
> 43.54"/>
> +</g>
> +<!-- egress_port -->
> +<g id="node10" class="node">
> +<title>egress_port</title>
> +<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
> +<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-
> size="14.00">egress_port</text>
> +</g>
> +<!-- ethdev_tx&#45;&gt;egress_port -->
> +<g id="edge14" class="edge">
> +<title>ethdev_tx&#45;&gt;egress_port</title>
> +<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-
> 40.12"/>
> +<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21
> 101.03,-36.78"/>
> +<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-
> size="14.00">egress packet</text>
> +</g>
> +</g>
> +</svg>
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index f2afb1fcc5..4f4dc8b518 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -23,4 +23,5 @@ DPDK Tools User Guides
>      testeventdev
>      testregex
>      testmldev
> +    graph
>      dts
> --
> 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 01/14] app/graph: add application framework to read CLI
  2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
  2023-09-09  1:18       ` [EXT] " Nithin Kumar Dabilpuram
@ 2023-09-19 16:04       ` skori
  2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
                           ` (14 more replies)
  1 sibling, 15 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v3..v4:
 - Addressed Nithin's review comments

 app/graph/cli.c        | 113 ++++++++++++++++++++++++++++++++++++
 app/graph/cli.h        |  32 +++++++++++
 app/graph/main.c       | 127 +++++++++++++++++++++++++++++++++++++++++
 app/graph/meson.build  |  14 +++++
 app/graph/module_api.h |  16 ++++++
 app/meson.build        |   1 +
 6 files changed, 303 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..473fa1635a
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..a4af1ec7e1
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
\ No newline at end of file
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..87c4ce30a8
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 02/14] app/graph: add telnet connectivity framework
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
@ 2023-09-19 16:04         ` skori
  2023-09-20  4:34           ` Jerin Jacob
  2023-09-19 16:04         ` [PATCH v4 03/14] app/graph: add parser utility APIs skori
                           ` (13 subsequent siblings)
  14 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds framework to initate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/conn.c       | 284 +++++++++++++++++++++++++++++++++++++++++
 app/graph/conn.h       |  46 +++++++
 app/graph/main.c       |  75 ++++++++++-
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   2 +
 5 files changed, 403 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..dabc8deca2
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	rc = listen(fd_server, 16);
+	if (rc == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1) {
+		conn_free(conn);
+		close(fd_server);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index a4af1ec7e1..79575fa2a6 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -16,13 +16,26 @@
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -41,7 +54,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -58,10 +71,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -117,10 +166,26 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		return -1;
+	};
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
 	}
 
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 87c4ce30a8..7e1ab0a214 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -10,5 +10,6 @@ endif
 deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..b927de9470 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,7 +7,9 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 03/14] app/graph: add parser utility APIs
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
  2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 04/14] app/graph: add mempool command line interfaces skori
                           ` (12 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index 7e1ab0a214..ed3bcb1567 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,4 +12,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b927de9470..acf63d15fa 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 04/14] app/graph: add mempool command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
  2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
  2023-09-19 16:04         ` [PATCH v4 03/14] app/graph: add parser utility APIs skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 05/14] app/graph: add ethdev " skori
                           ` (11 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c          |   2 +
 app/graph/mempool.c      | 140 +++++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h      |  24 +++++++
 app/graph/mempool_priv.h |  34 ++++++++++
 app/graph/meson.build    |   1 +
 app/graph/module_api.h   |   2 +
 6 files changed, 203 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 473fa1635a..c9f932517e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..901f07f461
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	strcpy(config.name, res->name);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed3bcb1567..d35287be14 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,5 +12,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index acf63d15fa..de154f5f93 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,10 +10,12 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 #endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 05/14] app/graph: add ethdev command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (2 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 04/14] app/graph: add mempool command line interfaces skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 06/14] app/graph: add ipv4_lookup " skori
                           ` (10 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c         |   8 +
 app/graph/ethdev.c      | 882 ++++++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h      |  40 ++
 app/graph/ethdev_priv.h | 112 +++++
 app/graph/main.c        |   3 +-
 app/graph/meson.build   |   1 +
 app/graph/module_api.h  |   1 +
 7 files changed, 1046 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c9f932517e..c4b5cf3ce1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..81ad8c4757
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	int numa_node, rc;
+	uint16_t port_id = 0;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 79575fa2a6..6368019712 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -186,7 +186,8 @@ main(int argc, char **argv)
 	}
 
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
-}
\ No newline at end of file
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d35287be14..d0369f1c09 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index de154f5f93..d509f38837 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 06/14] app/graph: add ipv4_lookup command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (3 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 05/14] app/graph: add ethdev " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 07/14] app/graph: add ipv6_lookup " skori
                           ` (9 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c        |   2 +
 app/graph/ethdev.c     |   2 +-
 app/graph/ip4_route.c  | 205 +++++++++++++++++++++++++++++++++++++++++
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/route.h      |  26 ++++++
 app/graph/route_priv.h |  44 +++++++++
 7 files changed, 280 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c4b5cf3ce1..430750db6e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 81ad8c4757..cb29c9054d 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..a8fb8ba8d5
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		portid = ethdev_portid_by_ip4(route->via, route->netmask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+
+		depth = convert_netmask_to_depth(route->netmask);
+
+		rc = rte_node_ip4_route_add(route->ip, depth, portid,
+				RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d0369f1c09..b993d6375f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index d509f38837..da049a1faa 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 07/14] app/graph: add ipv6_lookup command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (4 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 06/14] app/graph: add ipv4_lookup " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 08/14] app/graph: add neigh " skori
                           ` (8 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c       |   2 +
 app/graph/ethdev.c    |   1 +
 app/graph/ip6_route.c | 210 ++++++++++++++++++++++++++++++++++++++++++
 app/graph/meson.build |   1 +
 app/graph/route.h     |  14 +++
 5 files changed, 228 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 430750db6e..7213a91ad2 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index cb29c9054d..0a4eb7be4f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..93c2c967aa
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	int j;
+
+	struct route_ipv6_config *ipv6route;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	return 0;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route6, next) {
+
+		portid = ethdev_portid_by_ip6(route->gateway, route->mask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+		depth = convert_ip6_netmask_to_depth(route->mask);
+
+		rc = rte_node_ip6_route_add(route->ip, depth, portid,
+					     RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index b993d6375f..6841a25eb6 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 08/14] app/graph: add neigh command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (5 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 07/14] app/graph: add ipv6_lookup " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 09/14] app/graph: add ethdev_rx " skori
                           ` (7 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c        |   3 +
 app/graph/ethdev.c     |   2 +
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   2 +
 app/graph/neigh.c      | 331 +++++++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h      |  17 +++
 app/graph/neigh_priv.h |  49 ++++++
 7 files changed, 405 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 7213a91ad2..36338d5173 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 0a4eb7be4f..fc0bd8f05f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6841a25eb6..c0b8418b89 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,5 +16,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index da049a1faa..660d0d2f34 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..f59d61152b
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	int j;
+	struct neigh_ipv6_config *v6_config;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv4_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		portid = ethdev_portid_by_ip4(neigh->ip, 0);
+		if (portid < 0) {
+			printf("Invalid portid found to add  neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv6_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+
+		portid = ethdev_portid_by_ip6(neigh->ip, NULL);
+		if (portid < 0) {
+			printf("Invalid portid found to add neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n",
+				rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 09/14] app/graph: add ethdev_rx command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (6 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 08/14] app/graph: add neigh " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 10/14] app/graph: add graph " skori
                           ` (6 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 155 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 ++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 6 files changed, 235 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 36338d5173..e947f61ee4 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..8cd1c323e3
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c0b8418b89..caf2d20735 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 660d0d2f34..0a4e383f50 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 10/14] app/graph: add graph command line interfaces
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (7 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 09/14] app/graph: add ethdev_rx " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 11/14] app/graph: add CLI option to enable graph stats skori
                           ` (5 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds graph module to create a graph for a given usecase like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c        |   4 +
 app/graph/graph.c      | 473 +++++++++++++++++++++++++++++++++++++++++
 app/graph/graph.h      |  18 ++
 app/graph/graph_priv.h |  61 ++++++
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 6 files changed, 558 insertions(+)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index e947f61ee4..c43af5925c 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..d44c5f0464
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,473 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strcpy(graph_config.usecases[j].name, token);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+
+	return 0;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "graph");
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..f431ae10d2
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..c61e20672a
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t model_name;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index caf2d20735..6292fc9684 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 0a4e383f50..8417c2d3dd 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 11/14] app/graph: add CLI option to enable graph stats
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (8 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 10/14] app/graph: add graph " skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 12/14] app/graph: add l3fwd usecase skori
                           ` (4 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/main.c       | 17 ++++++++++++++++-
 app/graph/module_api.h |  2 ++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index 6368019712..de583ac0a4 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -18,12 +18,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -37,6 +38,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -53,6 +55,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -130,6 +133,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -141,6 +150,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 int
 main(int argc, char **argv)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 8417c2d3dd..9bb4b39edd 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,4 +24,6 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
+
 #endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 12/14] app/graph: add l3fwd usecase
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (9 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 11/14] app/graph: add CLI option to enable graph stats skori
@ 2023-09-19 16:04         ` skori
  2023-09-19 16:04         ` [PATCH v4 13/14] doc: add graph application user guide skori
                           ` (3 subsequent siblings)
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds an usecase l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli |  87 ++++++++++++++++++++++
 app/graph/graph.c            |   2 +-
 app/graph/l3fwd.c            | 138 +++++++++++++++++++++++++++++++++++
 app/graph/l3fwd.h            |  11 +++
 app/graph/meson.build        |   1 +
 app/graph/module_api.h       |   1 +
 6 files changed, 239 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..6b0d9ac770
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index d44c5f0464..659ed654bd 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -257,7 +257,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..5ab57dfa00
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
+static uint64_t packet_to_capture;
+static int pcap_trace_enable;
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t lcore_id;
+	int rc;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_conf.pcap_enable = pcap_trace_enable;
+	graph_conf.num_pkt_to_capture = packet_to_capture;
+	graph_conf.pcap_filename = pcap_filename;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6292fc9684..d8434fc90f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,6 +16,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9bb4b39edd..73f013143f 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 13/14] doc: add graph application user guide
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (10 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 12/14] app/graph: add l3fwd usecase skori
@ 2023-09-19 16:04         ` skori
  2023-09-20  4:28           ` Jerin Jacob
  2023-09-19 16:04         ` [PATCH v4 14/14] maintainers: add maintainers for graph app skori
                           ` (2 subsequent siblings)
  14 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds application user guide with detailed infomration about
application's parameter, exposed commands by each module etc.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 doc/guides/tools/graph.rst                   | 240 +++++++++++++++++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++++++++++++
 doc/guides/tools/index.rst                   |   1 +
 3 files changed, 451 insertions(+)
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..52d732cc88
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,240 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ * l3fwd
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+     - Command to express the desired use case
+     - No
+     - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - No
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - No
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - No
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - No
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - No
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - No
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
+
+Runtime configuration
+---------------------
+
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IPv4 address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
+
+   graph>
+   graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v4 14/14] maintainers: add maintainers for graph app
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (11 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 13/14] doc: add graph application user guide skori
@ 2023-09-19 16:04         ` skori
  2023-09-20  4:40         ` [PATCH v4 01/14] app/graph: add application framework to read CLI Jerin Jacob
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
  14 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-19 16:04 UTC (permalink / raw)
  To: Thomas Monjalon; +Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

Add Sunil Kumar Kori and Rakesh Kudurumalla as maintainers
for graph application.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 698608cdb2..7f149bd060 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1806,6 +1806,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v4 13/14] doc: add graph application user guide
  2023-09-19 16:04         ` [PATCH v4 13/14] doc: add graph application user guide skori
@ 2023-09-20  4:28           ` Jerin Jacob
  0 siblings, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-09-20  4:28 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Wed, Sep 20, 2023 at 6:31 AM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> It adds application user guide with detailed infomration about
> application's parameter, exposed commands by each module etc.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  doc/guides/tools/graph.rst                   | 240 +++++++++++++++++++
>  doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++++++++++++
>  doc/guides/tools/index.rst                   |   1 +

Based on latest contributions, No separate patch neede for doc,
please add the doc skelton in first patch and add documentation as
and when features are added to the patch.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v4 02/14] app/graph: add telnet connectivity framework
  2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
@ 2023-09-20  4:34           ` Jerin Jacob
  2023-09-20  8:14             ` Bruce Richardson
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-09-20  4:34 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Tue, Sep 19, 2023 at 9:35 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds framework to initate a telnet session with application.
>
> Some configurations and debug commands are exposed as runtime APIs.
> Those commands can be invoked using telnet session.
>
> Application initiates a telnet server with host address 0.0.0.0
> and port number 8086 by default.
>
> To make it configurable, "-h" and "-p" options are provided.
> Using them user can pass host address and port number on which
> application will start telnet server.
>
> Using same host address and port number, telnet client can connect
> to application.
>
> Syntax to connect with application:
>         # telnet <host> <port>
>
> Once session is connected, "graph> " prompt will be available.
> Example:
>         # telnet 10.28.35.207 50000
>           Trying 10.28.35.207...
>           Connected to 10.28.35.207.
>           Escape character is '^]'.
>
>           Welcome!
>
>           graph>

Some improvements
1) Please squash 14/14 patch to 1/14.
2) Ctrl - C doesn't work which is a serious issue. We have to kill it
via kill -9 <pid> from a separate window. This is probably because of
command line library.
3). In case app launch fails due to error, it will leave terminal into
a bad state until "reset" command is executed. This might also be
because of command line library.
4). Just a wishlist: If on terminal console I could do a tab and get
commands help, just like testpmd console.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v4 01/14] app/graph: add application framework to read CLI
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (12 preceding siblings ...)
  2023-09-19 16:04         ` [PATCH v4 14/14] maintainers: add maintainers for graph app skori
@ 2023-09-20  4:40         ` Jerin Jacob
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
  14 siblings, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-09-20  4:40 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Tue, Sep 19, 2023 at 9:35 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds base framework to read a given .cli file as a command line
> parameter "-s".
>
> Example:
>  # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli
>
> Each .cli file will contain commands to configure different module like
> mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
> commandline library.
>
> Each module needs to expose its supported commands & corresponding
> callback functions to commandline library to get them parsed.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
> v3..v4:
>  - Addressed Nithin's review comments

List the review comments for next versions or minimum, share the
patchwork URL for review comments.


> +               return -EINVAL;
> +
> +       msg_in = malloc(msg_in_len_max + 1);
> +       msg_out = malloc(msg_out_len_max + 1);
> +       if ((msg_in == NULL) || (msg_out == NULL)) {
> +               free(msg_out);
> +               free(msg_in);
> +               return -ENOMEM;

Please switch to goto exit; scheme.

> +       }
> +
> +       /* Open input file */
> +       f = fopen(file_name, "r");
> +       if (f == NULL) {
> +               free(msg_out);
> +               free(msg_in);
> +               return -EIO;

Same as here.

> +       }

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v4 02/14] app/graph: add telnet connectivity framework
  2023-09-20  4:34           ` Jerin Jacob
@ 2023-09-20  8:14             ` Bruce Richardson
  0 siblings, 0 replies; 182+ messages in thread
From: Bruce Richardson @ 2023-09-20  8:14 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: skori, Rakesh Kudurumalla, dev

On Wed, Sep 20, 2023 at 10:04:55AM +0530, Jerin Jacob wrote:
> On Tue, Sep 19, 2023 at 9:35 PM <skori@marvell.com> wrote:
> >
> > From: Sunil Kumar Kori <skori@marvell.com>
> >
> > It adds framework to initate a telnet session with application.
> >
> > Some configurations and debug commands are exposed as runtime APIs.
> > Those commands can be invoked using telnet session.
> >
> > Application initiates a telnet server with host address 0.0.0.0
> > and port number 8086 by default.
> >
> > To make it configurable, "-h" and "-p" options are provided.
> > Using them user can pass host address and port number on which
> > application will start telnet server.
> >
> > Using same host address and port number, telnet client can connect
> > to application.
> >
> > Syntax to connect with application:
> >         # telnet <host> <port>
> >
> > Once session is connected, "graph> " prompt will be available.
> > Example:
> >         # telnet 10.28.35.207 50000
> >           Trying 10.28.35.207...
> >           Connected to 10.28.35.207.
> >           Escape character is '^]'.
> >
> >           Welcome!
> >
> >           graph>
> 
> Some improvements
> 1) Please squash 14/14 patch to 1/14.
> 2) Ctrl - C doesn't work which is a serious issue. We have to kill it
> via kill -9 <pid> from a separate window. This is probably because of
> command line library.
> 3). In case app launch fails due to error, it will leave terminal into
> a bad state until "reset" command is executed. This might also be
> because of command line library.
> 4). Just a wishlist: If on terminal console I could do a tab and get
> commands help, just like testpmd console.

I think that the tab completion is only available if you use
cmdline_stdin_new vs regular cmdline creation function. In the case of the
telnet connection, it may work to set the telnet fd as stdin/stdout for the
connection and then use cmdline_stdin_new to create the cmdline.

/Bruce

^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 00/12] add CLI based graph application
  2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
                           ` (13 preceding siblings ...)
  2023-09-20  4:40         ` [PATCH v4 01/14] app/graph: add application framework to read CLI Jerin Jacob
@ 2023-09-21 10:08         ` skori
  2023-09-21 10:08           ` [PATCH v5 01/12] app/graph: add application framework to read CLI skori
                             ` (11 more replies)
  14 siblings, 12 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.


Rakesh Kudurumalla (5):
  app/graph: add mempool command line interfaces
  app/graph: add ipv6_lookup command line interfaces
  app/graph: add ethdev_rx command line interfaces
  app/graph: add graph command line interfaces
  app/graph: add l3fwd usecase

Sunil Kumar Kori (7):
  app/graph: add application framework to read CLI
  app/graph: add telnet connectivity framework
  app/graph: add parser utility APIs
  app/graph: add ethdev command line interfaces
  app/graph: add ipv4_lookup command line interfaces
  app/graph: add neigh command line interfaces
  app/graph: add CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 136 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 282 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  87 ++
 app/graph/graph.c                            | 537 +++++++++++
 app/graph/graph.h                            |  20 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 205 +++++
 app/graph/ip6_route.c                        | 210 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 235 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  24 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 331 +++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/tools/graph.rst                   | 241 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 36 files changed, 4649 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 01/12] app/graph: add application framework to read CLI
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 02/12] app/graph: add telnet connectivity framework skori
                             ` (10 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

 MAINTAINERS                |   7 ++
 app/graph/cli.c            | 113 +++++++++++++++++++++++++++++++++
 app/graph/cli.h            |  32 ++++++++++
 app/graph/main.c           | 127 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |  14 ++++
 app/graph/module_api.h     |  16 +++++
 app/meson.build            |   1 +
 doc/guides/tools/graph.rst |  82 ++++++++++++++++++++++++
 doc/guides/tools/index.rst |   1 +
 9 files changed, 393 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 698608cdb2..7f149bd060 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1806,6 +1806,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..473fa1635a
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..a4af1ec7e1
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
\ No newline at end of file
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..87c4ce30a8
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..271a85896d
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,82 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ *
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - Dummy command
+     - Dummy command description
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 02/12] app/graph: add telnet connectivity framework
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
  2023-09-21 10:08           ` [PATCH v5 01/12] app/graph: add application framework to read CLI skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 03/12] app/graph: add parser utility APIs skori
                             ` (9 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds framework to initate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/conn.c           | 282 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           |  81 ++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 445 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..d680e3c635
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse,
+				sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index a4af1ec7e1..607f8a28f1 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -16,13 +16,26 @@
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -41,7 +54,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -58,10 +71,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -117,10 +166,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 87c4ce30a8..7e1ab0a214 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -10,5 +10,6 @@ endif
 deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..b927de9470 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,7 +7,9 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 271a85896d..26a7982722 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -41,6 +41,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -74,7 +84,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IPv4 address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 03/12] app/graph: add parser utility APIs
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
  2023-09-21 10:08           ` [PATCH v5 01/12] app/graph: add application framework to read CLI skori
  2023-09-21 10:08           ` [PATCH v5 02/12] app/graph: add telnet connectivity framework skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 04/12] app/graph: add mempool command line interfaces skori
                             ` (8 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index 7e1ab0a214..ed3bcb1567 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,4 +12,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b927de9470..acf63d15fa 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 04/12] app/graph: add mempool command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (2 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 03/12] app/graph: add parser utility APIs skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 05/12] app/graph: add ethdev " skori
                             ` (7 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   8 +++
 7 files changed, 211 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 473fa1635a..c9f932517e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..901f07f461
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	strcpy(config.name, res->name);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed3bcb1567..d35287be14 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,5 +12,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index acf63d15fa..de154f5f93 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,10 +10,12 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 26a7982722..9f6b83e248 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -80,6 +80,14 @@ file to express the requested use case configuration.
      - Dummy command description
      - No
      - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 05/12] app/graph: add ethdev command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (3 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 04/12] app/graph: add mempool command line interfaces skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 06/12] app/graph: add ipv4_lookup " skori
                             ` (6 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   3 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  47 ++
 8 files changed, 1093 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c9f932517e..c4b5cf3ce1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..81ad8c4757
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	int numa_node, rc;
+	uint16_t port_id = 0;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 607f8a28f1..b3b8b9a2d7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -192,7 +192,8 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
-}
\ No newline at end of file
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d35287be14..d0369f1c09 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index de154f5f93..d509f38837 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9f6b83e248..09416a32ce 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -88,6 +88,42 @@ file to express the requested use case configuration.
      - Command to dump mempool help message
      - Yes
      - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - No
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - No
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
@@ -121,6 +157,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (4 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 05/12] app/graph: add ethdev " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 07/12] app/graph: add ipv6_lookup " skori
                             ` (5 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 205 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c4b5cf3ce1..430750db6e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 81ad8c4757..cb29c9054d 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..a8fb8ba8d5
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		portid = ethdev_portid_by_ip4(route->via, route->netmask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+
+		depth = convert_netmask_to_depth(route->netmask);
+
+		rc = rte_node_ip4_route_add(route->ip, depth, portid,
+				RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d0369f1c09..b993d6375f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index d509f38837..da049a1faa 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 09416a32ce..e2b4a3dfd5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -124,6 +124,15 @@ file to express the requested use case configuration.
      - Command to dump ethdev help message
      - Yes
      - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - No
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 07/12] app/graph: add ipv6_lookup command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (5 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 06/12] app/graph: add ipv4_lookup " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 08/12] app/graph: add neigh " skori
                             ` (4 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 210 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 237 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 430750db6e..7213a91ad2 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index cb29c9054d..0a4eb7be4f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..93c2c967aa
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	int j;
+
+	struct route_ipv6_config *ipv6route;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	return 0;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route6, next) {
+
+		portid = ethdev_portid_by_ip6(route->gateway, route->mask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+		depth = convert_ip6_netmask_to_depth(route->mask);
+
+		rc = rte_node_ip6_route_add(route->ip, depth, portid,
+					     RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index b993d6375f..6841a25eb6 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index e2b4a3dfd5..0782eb3461 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -133,6 +133,15 @@ file to express the requested use case configuration.
      - Command to dump ipv4_lookup help message
      - Yes
      - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - No
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 08/12] app/graph: add neigh command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (6 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 07/12] app/graph: add ipv6_lookup " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 09/12] app/graph: add ethdev_rx " skori
                             ` (3 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 331 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 ++++++
 doc/guides/tools/graph.rst |  12 ++
 8 files changed, 417 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 7213a91ad2..36338d5173 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 0a4eb7be4f..fc0bd8f05f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6841a25eb6..c0b8418b89 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,5 +16,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index da049a1faa..660d0d2f34 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..f59d61152b
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	int j;
+	struct neigh_ipv6_config *v6_config;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv4_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		portid = ethdev_portid_by_ip4(neigh->ip, 0);
+		if (portid < 0) {
+			printf("Invalid portid found to add  neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv6_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+
+		portid = ethdev_portid_by_ip6(neigh->ip, NULL);
+		if (portid < 0) {
+			printf("Invalid portid found to add neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n",
+				rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 0782eb3461..c46c8c5737 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -142,6 +142,18 @@ file to express the requested use case configuration.
      - Command to dump ipv6_lookup help message
      - Yes
      - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - No
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - No
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 09/12] app/graph: add ethdev_rx command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (7 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 08/12] app/graph: add neigh " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 10/12] app/graph: add graph " skori
                             ` (2 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 36338d5173..e947f61ee4 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..03f8effcca
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = graph_coremask_get();
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c0b8418b89..caf2d20735 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 660d0d2f34..0a4e383f50 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index c46c8c5737..56d8a08e5c 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -154,6 +154,16 @@ file to express the requested use case configuration.
      - Command to dump neigh help message
      - Yes
      - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 10/12] app/graph: add graph command line interfaces
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (8 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 09/12] app/graph: add ethdev_rx " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 11/12] app/graph: add CLI option to enable graph stats skori
  2023-09-21 10:08           ` [PATCH v5 12/12] app/graph: add l3fwd usecase skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds graph module to create a graph for a given usecase like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/graph.c          | 537 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  20 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  19 +-
 7 files changed, 650 insertions(+), 2 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index e947f61ee4..c43af5925c 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..c3ed2c3f2d
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,537 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strcpy(graph_config.usecases[j].name, token);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if(rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= 1 << lcore_id;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num) {
+				continue;
+			} else {
+				return -EINVAL;
+			}
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..1c3599ac8c
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index caf2d20735..6292fc9684 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 0a4e383f50..8417c2d3dd 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 56d8a08e5c..2c06f8e958 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -76,10 +76,25 @@ file to express the requested use case configuration.
      - Description
      - Dynamic
      - Optional
-   * - Dummy command
-     - Dummy command description
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+       pcap_enable <0 | 1> num_pcap_pkts <num> pcap_file <output_capture_file>
+     - Command to express the desired use case. Also enables/disable pcap capturing
      - No
      - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
    * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
      - Command to create mempool which will be further associated to RxQ to dequeue the packets
      - No
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 11/12] app/graph: add CLI option to enable graph stats
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (9 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 10/12] app/graph: add graph " skori
@ 2023-09-21 10:08           ` skori
  2023-09-21 10:08           ` [PATCH v5 12/12] app/graph: add l3fwd usecase skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/main.c           | 38 +++++++++++++++++++++++++++++++++++++-
 app/graph/module_api.h     |  3 +++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index b3b8b9a2d7..177e15a556 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -18,12 +18,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -37,6 +38,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -53,6 +55,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -130,6 +133,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -141,6 +150,33 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
+
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 8417c2d3dd..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,4 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
+bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 2c06f8e958..649e802d73 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -57,6 +57,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v5 12/12] app/graph: add l3fwd usecase
  2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
                             ` (10 preceding siblings ...)
  2023-09-21 10:08           ` [PATCH v5 11/12] app/graph: add CLI option to enable graph stats skori
@ 2023-09-21 10:08           ` skori
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
  11 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-21 10:08 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds an usecase l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  87 ++++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |   9 +-
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 8 files changed, 455 insertions(+), 2 deletions(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..1038fde04e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index c3ed2c3f2d..f8859ccb8c 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -258,7 +258,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..9144efb200
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	uint8_t lcore_id, pcap_ena;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	char *pcap_file;
+	int rc;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6292fc9684..d8434fc90f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,6 +16,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 649e802d73..e32da251ad 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,7 +12,7 @@ Based on the input file, application creates a graph to cater the use case.
 
 Supported Use cases
 -------------------
- *
+ * l3fwd
 
 Running the Application
 -----------------------
@@ -232,3 +232,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 00/12] add CLI based graph application
  2023-09-21 10:08           ` [PATCH v5 12/12] app/graph: add l3fwd usecase skori
@ 2023-09-26 10:57             ` skori
  2023-09-26 10:57               ` [PATCH v6 01/12] app/graph: add application framework to read CLI skori
                                 ` (11 more replies)
  0 siblings, 12 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: add mempool command line interfaces
  app/graph: add ipv6_lookup command line interfaces
  app/graph: add ethdev_rx command line interfaces
  app/graph: add graph command line interfaces
  app/graph: add l3fwd use case

Sunil Kumar Kori (7):
  app/graph: add application framework to read CLI
  app/graph: add telnet connectivity framework
  app/graph: add parser utility APIs
  app/graph: add ethdev command line interfaces
  app/graph: add ipv4_lookup command line interfaces
  app/graph: add neigh command line interfaces
  app/graph: add CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 136 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 282 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  87 ++
 app/graph/graph.c                            | 536 +++++++++++
 app/graph/graph.h                            |  20 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 205 +++++
 app/graph/ip6_route.c                        | 210 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  24 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 331 +++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/tools/graph.rst                   | 241 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 36 files changed, 4650 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 01/12] app/graph: add application framework to read CLI
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 02/12] app/graph: add telnet connectivity framework skori
                                 ` (10 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                |   7 ++
 app/graph/cli.c            | 113 ++++++++++++++++++++++++++++++++
 app/graph/cli.h            |  32 ++++++++++
 app/graph/main.c           | 128 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |  14 ++++
 app/graph/module_api.h     |  16 +++++
 app/meson.build            |   1 +
 doc/guides/tools/graph.rst |  82 ++++++++++++++++++++++++
 doc/guides/tools/index.rst |   1 +
 9 files changed, 394 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 00f5a5f9e6..7998be98f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1811,6 +1811,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..473fa1635a
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..87c4ce30a8
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..271a85896d
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,82 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ *
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - Dummy command
+     - Dummy command description
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 02/12] app/graph: add telnet connectivity framework
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
  2023-09-26 10:57               ` [PATCH v6 01/12] app/graph: add application framework to read CLI skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 03/12] app/graph: add parser utility APIs skori
                                 ` (9 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/conn.c           | 282 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 468 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..8c88500605
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 87c4ce30a8..7e1ab0a214 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -10,5 +10,6 @@ endif
 deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 271a85896d..26a7982722 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -41,6 +41,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -74,7 +84,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IPv4 address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 03/12] app/graph: add parser utility APIs
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
  2023-09-26 10:57               ` [PATCH v6 01/12] app/graph: add application framework to read CLI skori
  2023-09-26 10:57               ` [PATCH v6 02/12] app/graph: add telnet connectivity framework skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 04/12] app/graph: add mempool command line interfaces skori
                                 ` (8 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index 7e1ab0a214..ed3bcb1567 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,4 +12,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 04/12] app/graph: add mempool command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (2 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 03/12] app/graph: add parser utility APIs skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 05/12] app/graph: add ethdev " skori
                                 ` (7 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   8 +++
 7 files changed, 211 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 473fa1635a..c9f932517e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..901f07f461
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	strcpy(config.name, res->name);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed3bcb1567..d35287be14 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,5 +12,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 26a7982722..9f6b83e248 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -80,6 +80,14 @@ file to express the requested use case configuration.
      - Dummy command description
      - No
      - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 05/12] app/graph: add ethdev command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (3 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 04/12] app/graph: add mempool command line interfaces skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 06/12] app/graph: add ipv4_lookup " skori
                                 ` (6 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  47 ++
 8 files changed, 1092 insertions(+)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c9f932517e..c4b5cf3ce1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..81ad8c4757
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	int numa_node, rc;
+	uint16_t port_id = 0;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d35287be14..d0369f1c09 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9f6b83e248..09416a32ce 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -88,6 +88,42 @@ file to express the requested use case configuration.
      - Command to dump mempool help message
      - Yes
      - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - No
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - No
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
@@ -121,6 +157,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (4 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 05/12] app/graph: add ethdev " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 07/12] app/graph: add ipv6_lookup " skori
                                 ` (5 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 205 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c4b5cf3ce1..430750db6e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 81ad8c4757..cb29c9054d 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..a8fb8ba8d5
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		portid = ethdev_portid_by_ip4(route->via, route->netmask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+
+		depth = convert_netmask_to_depth(route->netmask);
+
+		rc = rte_node_ip4_route_add(route->ip, depth, portid,
+				RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d0369f1c09..b993d6375f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 09416a32ce..e2b4a3dfd5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -124,6 +124,15 @@ file to express the requested use case configuration.
      - Command to dump ethdev help message
      - Yes
      - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - No
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 07/12] app/graph: add ipv6_lookup command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (5 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 06/12] app/graph: add ipv4_lookup " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 08/12] app/graph: add neigh " skori
                                 ` (4 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 210 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 237 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 430750db6e..7213a91ad2 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index cb29c9054d..0a4eb7be4f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..93c2c967aa
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	int j;
+
+	struct route_ipv6_config *ipv6route;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	return 0;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+	uint8_t depth;
+	int portid;
+
+	TAILQ_FOREACH(route, &route6, next) {
+
+		portid = ethdev_portid_by_ip6(route->gateway, route->mask);
+		if (portid < 0) {
+			printf("Invalid portid found to install the route\n");
+			return rc;
+		}
+		depth = convert_ip6_netmask_to_depth(route->mask);
+
+		rc = rte_node_ip6_route_add(route->ip, depth, portid,
+					     RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index b993d6375f..6841a25eb6 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index e2b4a3dfd5..0782eb3461 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -133,6 +133,15 @@ file to express the requested use case configuration.
      - Command to dump ipv4_lookup help message
      - Yes
      - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - No
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 08/12] app/graph: add neigh command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (6 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 07/12] app/graph: add ipv6_lookup " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 09/12] app/graph: add ethdev_rx " skori
                                 ` (3 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 331 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 ++++++
 doc/guides/tools/graph.rst |  12 ++
 8 files changed, 417 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 7213a91ad2..36338d5173 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 0a4eb7be4f..fc0bd8f05f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6841a25eb6..c0b8418b89 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,5 +16,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..f59d61152b
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	int j;
+	struct neigh_ipv6_config *v6_config;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv4_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		portid = ethdev_portid_by_ip4(neigh->ip, 0);
+		if (portid < 0) {
+			printf("Invalid portid found to add  neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	struct neigh_ipv6_config *neigh;
+	int16_t portid = 0;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+
+		portid = ethdev_portid_by_ip6(neigh->ip, NULL);
+		if (portid < 0) {
+			printf("Invalid portid found to add neigh\n");
+			return -EINVAL;
+		}
+
+		memset(data, 0, len);
+
+		/* Copy dst mac */
+		rte_memcpy((void *)&data[0], (void *)&neigh->mac, RTE_ETHER_ADDR_LEN);
+
+		/* Copy src mac */
+		rc = rte_eth_macaddr_get(portid, &smac);
+		if (rc < 0) {
+			printf("Cannot get MAC address: err=%d, port=%d\n",
+				rc, portid);
+			return rc;
+		}
+
+		rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+		rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
+		if (rc < 0) {
+			printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 0782eb3461..c46c8c5737 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -142,6 +142,18 @@ file to express the requested use case configuration.
      - Command to dump ipv6_lookup help message
      - Yes
      - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - No
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - No
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 09/12] app/graph: add ethdev_rx command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (7 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 08/12] app/graph: add neigh " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 10/12] app/graph: add graph " skori
                                 ` (2 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 36338d5173..e947f61ee4 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c0b8418b89..caf2d20735 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,6 +11,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index c46c8c5737..56d8a08e5c 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -154,6 +154,16 @@ file to express the requested use case configuration.
      - Command to dump neigh help message
      - Yes
      - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 10/12] app/graph: add graph command line interfaces
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (8 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 09/12] app/graph: add ethdev_rx " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 11/12] app/graph: add CLI option to enable graph stats skori
  2023-09-26 10:57               ` [PATCH v6 12/12] app/graph: add l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 536 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  20 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  19 +-
 8 files changed, 650 insertions(+), 3 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index e947f61ee4..c43af5925c 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..1e543ea7aa
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,536 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strcpy(graph_config.usecases[j].name, token);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= 1 << lcore_id;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..1c3599ac8c
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index caf2d20735..6292fc9684 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 56d8a08e5c..2c06f8e958 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -76,10 +76,25 @@ file to express the requested use case configuration.
      - Description
      - Dynamic
      - Optional
-   * - Dummy command
-     - Dummy command description
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+       pcap_enable <0 | 1> num_pcap_pkts <num> pcap_file <output_capture_file>
+     - Command to express the desired use case. Also enables/disable pcap capturing
      - No
      - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
    * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
      - Command to create mempool which will be further associated to RxQ to dequeue the packets
      - No
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 11/12] app/graph: add CLI option to enable graph stats
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (9 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 10/12] app/graph: add graph " skori
@ 2023-09-26 10:57               ` skori
  2023-09-26 10:57               ` [PATCH v6 12/12] app/graph: add l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 2c06f8e958..649e802d73 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -57,6 +57,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v6 12/12] app/graph: add l3fwd use case
  2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
                                 ` (10 preceding siblings ...)
  2023-09-26 10:57               ` [PATCH v6 11/12] app/graph: add CLI option to enable graph stats skori
@ 2023-09-26 10:57               ` skori
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
  11 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-26 10:57 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  87 ++++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |   9 +-
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 8 files changed, 455 insertions(+), 2 deletions(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..1038fde04e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index 1e543ea7aa..f4b18051b7 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -258,7 +258,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 6292fc9684..d8434fc90f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -16,6 +16,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 649e802d73..e32da251ad 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,7 +12,7 @@ Based on the input file, application creates a graph to cater the use case.
 
 Supported Use cases
 -------------------
- *
+ * l3fwd
 
 Running the Application
 -----------------------
@@ -232,3 +232,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 00/12] add CLI based graph application
  2023-09-26 10:57               ` [PATCH v6 12/12] app/graph: add l3fwd use case skori
@ 2023-09-27 11:54                 ` skori
  2023-09-27 11:54                   ` [PATCH v7 01/12] app/graph: add application framework to read CLI skori
                                     ` (11 more replies)
  0 siblings, 12 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: add mempool command line interfaces
  app/graph: add ipv6_lookup command line interfaces
  app/graph: add ethdev_rx command line interfaces
  app/graph: add graph command line interfaces
  app/graph: add l3fwd use case

Sunil Kumar Kori (7):
  app/graph: add application framework to read CLI
  app/graph: add telnet connectivity framework
  app/graph: add parser utility APIs
  app/graph: add ethdev command line interfaces
  app/graph: add ipv4_lookup command line interfaces
  app/graph: add neigh command line interfaces
  app/graph: add CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 136 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 282 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  87 ++
 app/graph/graph.c                            | 547 ++++++++++++
 app/graph/graph.h                            |  21 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 224 +++++
 app/graph/ip6_route.c                        | 229 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 366 ++++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/tools/graph.rst                   | 241 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 36 files changed, 4736 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 01/12] app/graph: add application framework to read CLI
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 02/12] app/graph: add telnet connectivity framework skori
                                     ` (10 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v6..v7:
 - Fix FreeBSD build error.
 - Make route and neigh runtime configuration too.

v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                |   7 ++
 app/graph/cli.c            | 113 ++++++++++++++++++++++++++++++++
 app/graph/cli.h            |  32 ++++++++++
 app/graph/main.c           | 128 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |  15 +++++
 app/graph/module_api.h     |  16 +++++
 app/meson.build            |   1 +
 doc/guides/tools/graph.rst |  82 ++++++++++++++++++++++++
 doc/guides/tools/index.rst |   1 +
 9 files changed, 395 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 00f5a5f9e6..7998be98f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1811,6 +1811,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..473fa1635a
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..08d0a48cd9
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..271a85896d
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,82 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ *
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - Dummy command
+     - Dummy command description
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 02/12] app/graph: add telnet connectivity framework
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
  2023-09-27 11:54                   ` [PATCH v7 01/12] app/graph: add application framework to read CLI skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 03/12] app/graph: add parser utility APIs skori
                                     ` (9 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/conn.c           | 282 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 468 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..8c88500605
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 08d0a48cd9..644e5c39f2 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@ endif
 deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 271a85896d..26a7982722 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -41,6 +41,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -74,7 +84,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IPv4 address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 03/12] app/graph: add parser utility APIs
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
  2023-09-27 11:54                   ` [PATCH v7 01/12] app/graph: add application framework to read CLI skori
  2023-09-27 11:54                   ` [PATCH v7 02/12] app/graph: add telnet connectivity framework skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 04/12] app/graph: add mempool command line interfaces skori
                                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index 644e5c39f2..d322f27d8e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,4 +13,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 04/12] app/graph: add mempool command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (2 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 03/12] app/graph: add parser utility APIs skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 05/12] app/graph: add ethdev " skori
                                     ` (7 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   8 +++
 7 files changed, 211 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 473fa1635a..c9f932517e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..901f07f461
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	strcpy(config.name, res->name);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d322f27d8e..2027183050 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,5 +13,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 26a7982722..9f6b83e248 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -80,6 +80,14 @@ file to express the requested use case configuration.
      - Dummy command description
      - No
      - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 05/12] app/graph: add ethdev command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (3 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 04/12] app/graph: add mempool command line interfaces skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 06/12] app/graph: add ipv4_lookup " skori
                                     ` (6 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  47 ++
 8 files changed, 1092 insertions(+)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c9f932517e..c4b5cf3ce1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..81ad8c4757
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	int numa_node, rc;
+	uint16_t port_id = 0;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 2027183050..aaaced4932 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9f6b83e248..0d810c2389 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -88,6 +88,42 @@ file to express the requested use case configuration.
      - Command to dump mempool help message
      - Yes
      - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - Yes
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
@@ -121,6 +157,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (4 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 05/12] app/graph: add ethdev " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 07/12] app/graph: add ipv6_lookup " skori
                                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c4b5cf3ce1..430750db6e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 81ad8c4757..cb29c9054d 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..db3354c270
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+
+	depth = convert_netmask_to_depth(ipv4route->netmask);
+
+	return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
+			RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+	int rc = -EINVAL;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route4_rewirte_table_update(ipv4route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+free:
+	free(ipv4route);
+	return rc;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		rc = route4_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index aaaced4932..fee22cae5f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 0d810c2389..018df7efde 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -124,6 +124,15 @@ file to express the requested use case configuration.
      - Command to dump ethdev help message
      - Yes
      - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - Yes
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 07/12] app/graph: add ipv6_lookup command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (5 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 06/12] app/graph: add ipv4_lookup " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 08/12] app/graph: add neigh " skori
                                     ` (4 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 253 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 430750db6e..7213a91ad2 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index cb29c9054d..0a4eb7be4f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..e793cde830
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+	depth = convert_ip6_netmask_to_depth(ipv6route->mask);
+
+	return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
+			RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+	int rc = -EINVAL;
+	int j;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route6_rewirte_table_update(ipv6route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
+	return 0;
+free:
+	free(ipv6route);
+	return rc;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route6, next) {
+		rc = route6_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index fee22cae5f..c3261a2162 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 018df7efde..9a4a9f9191 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -133,6 +133,15 @@ file to express the requested use case configuration.
      - Command to dump ipv4_lookup help message
      - Yes
      - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - Yes
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 08/12] app/graph: add neigh command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (6 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 07/12] app/graph: add ipv6_lookup " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 09/12] app/graph: add ethdev_rx " skori
                                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 360 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 +++++
 doc/guides/tools/graph.rst |  12 ++
 8 files changed, 446 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 7213a91ad2..36338d5173 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 0a4eb7be4f..fc0bd8f05f 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c3261a2162..39fe0b984f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,5 +17,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..90f62feb70
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,360 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
+	if (portid < 0) {
+		printf("Invalid portid found to add neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0)
+		return rc;
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip6_rewrite_add(portid, data, len, portid);
+
+
+}
+
+static int
+ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac = {0};
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip4(v4_config->ip, 0);
+	if (portid < 0) {
+		printf("Invalid portid found to add  neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0) {
+		printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+		return rc;
+	}
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip4_rewrite_add(portid, data, len, portid);
+}
+
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+	int rc = -EINVAL;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = ip4_rewrite_node_add(v4_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+free:
+	free(v4_config);
+	return rc;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+	int rc = -EINVAL;
+	int j;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc =  ip6_rewrite_node_add(v6_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+free:
+	free(v6_config);
+	return rc;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	struct neigh_ipv4_config *neigh;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		rc = ip4_rewrite_node_add(neigh);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	struct neigh_ipv6_config *neigh;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+		rc = ip6_rewrite_node_add(neigh);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9a4a9f9191..9ec801aa64 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -142,6 +142,18 @@ file to express the requested use case configuration.
      - Command to dump ipv6_lookup help message
      - Yes
      - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - Yes
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - Yes
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 09/12] app/graph: add ethdev_rx command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (7 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 08/12] app/graph: add neigh " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 10/12] app/graph: add graph " skori
                                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 36338d5173..e947f61ee4 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 39fe0b984f..cafab04f70 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9ec801aa64..f2920ff136 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -154,6 +154,16 @@ file to express the requested use case configuration.
      - Command to dump neigh help message
      - Yes
      - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 10/12] app/graph: add graph command line interfaces
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (8 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 09/12] app/graph: add ethdev_rx " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 11/12] app/graph: add CLI option to enable graph stats skori
  2023-09-27 11:54                   ` [PATCH v7 12/12] app/graph: add l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 547 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  21 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/ip4_route.c      |   5 +-
 app/graph/ip6_route.c      |   5 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/neigh.c          |  10 +-
 doc/guides/tools/graph.rst |  19 +-
 11 files changed, 678 insertions(+), 7 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index e947f61ee4..c43af5925c 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..f91ba640c6
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,547 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+bool graph_started;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strcpy(graph_config.usecases[j].name, token);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+bool
+graph_status_get(void)
+{
+	return graph_started;
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+	int rc = -EINVAL;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+
+	if (!rc)
+		graph_started = true;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= 1 << lcore_id;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..a14fa37ccd
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+bool graph_status_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
index db3354c270..fc83586427 100644
--- a/app/graph/ip4_route.c
+++ b/app/graph/ip4_route.c
@@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
 	ipv4route->via = route->via;
 	ipv4route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route4_rewirte_table_update(ipv4route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
 	return 0;
 free:
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
index e793cde830..1fa4865220 100644
--- a/app/graph/ip6_route.c
+++ b/app/graph/ip6_route.c
@@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
 	}
 	ipv6route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route6_rewirte_table_update(ipv6route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
 	return 0;
 free:
diff --git a/app/graph/meson.build b/app/graph/meson.build
index cafab04f70..c0b59b9a92 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
index 90f62feb70..1a241d8439 100644
--- a/app/graph/neigh.c
+++ b/app/graph/neigh.c
@@ -156,11 +156,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
 	v4_config->mac = mac;
 	v4_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = ip4_rewrite_node_add(v4_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
 	return 0;
 free:
@@ -189,11 +192,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
 	v6_config->mac = mac;
 	v6_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc =  ip6_rewrite_node_add(v6_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
 	return 0;
 free:
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index f2920ff136..7d2aa95c95 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -76,10 +76,25 @@ file to express the requested use case configuration.
      - Description
      - Dynamic
      - Optional
-   * - Dummy command
-     - Dummy command description
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+       pcap_enable <0 | 1> num_pcap_pkts <num> pcap_file <output_capture_file>
+     - Command to express the desired use case. Also enables/disable pcap capturing
      - No
      - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
    * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
      - Command to create mempool which will be further associated to RxQ to dequeue the packets
      - No
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 11/12] app/graph: add CLI option to enable graph stats
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (9 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 10/12] app/graph: add graph " skori
@ 2023-09-27 11:54                   ` skori
  2023-09-27 11:54                   ` [PATCH v7 12/12] app/graph: add l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 7d2aa95c95..d548cb67ec 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -57,6 +57,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v7 12/12] app/graph: add l3fwd use case
  2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
                                     ` (10 preceding siblings ...)
  2023-09-27 11:54                   ` [PATCH v7 11/12] app/graph: add CLI option to enable graph stats skori
@ 2023-09-27 11:54                   ` skori
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
  11 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-27 11:54 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  87 ++++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |   9 +-
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 8 files changed, 455 insertions(+), 2 deletions(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..1038fde04e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index f91ba640c6..4846c854c9 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -266,7 +266,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				rc  = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c0b59b9a92..27bac23ef0 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index d548cb67ec..5fa4c2a6d5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,7 +12,7 @@ Based on the input file, application creates a graph to cater the use case.
 
 Supported Use cases
 -------------------
- *
+ * l3fwd
 
 Running the Application
 -----------------------
@@ -232,3 +232,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 00/12] add CLI based graph application
  2023-09-27 11:54                   ` [PATCH v7 12/12] app/graph: add l3fwd use case skori
@ 2023-09-29  9:58                     ` skori
  2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
                                         ` (12 more replies)
  0 siblings, 13 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: add mempool command line interfaces
  app/graph: add ipv6_lookup command line interfaces
  app/graph: add ethdev_rx command line interfaces
  app/graph: add graph command line interfaces
  app/graph: add l3fwd use case

Sunil Kumar Kori (7):
  app/graph: add application framework to read CLI
  app/graph: add telnet connectivity framework
  app/graph: add parser utility APIs
  app/graph: add ethdev command line interfaces
  app/graph: add ipv4_lookup command line interfaces
  app/graph: add neigh command line interfaces
  app/graph: add CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 136 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 282 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  87 ++
 app/graph/graph.c                            | 550 ++++++++++++
 app/graph/graph.h                            |  21 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 224 +++++
 app/graph/ip6_route.c                        | 229 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 366 ++++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/tools/graph.rst                   | 241 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 36 files changed, 4739 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 01/12] app/graph: add application framework to read CLI
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16  9:00                         ` Jerin Jacob
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
  2023-09-29  9:58                       ` [PATCH v8 02/12] app/graph: add telnet connectivity framework skori
                                         ` (11 subsequent siblings)
  12 siblings, 2 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v7..v8:
 - Fix klocwork issues.

v6..v7:
 - Fix FreeBSD build error.
 - Make route and neigh runtime configuration too.

v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                |   7 ++
 app/graph/cli.c            | 113 ++++++++++++++++++++++++++++++++
 app/graph/cli.h            |  32 ++++++++++
 app/graph/main.c           | 128 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |  15 +++++
 app/graph/module_api.h     |  16 +++++
 app/meson.build            |   1 +
 doc/guides/tools/graph.rst |  82 ++++++++++++++++++++++++
 doc/guides/tools/index.rst |   1 +
 9 files changed, 395 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 00f5a5f9e6..7998be98f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1811,6 +1811,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..473fa1635a
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return -EINVAL;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		free(msg_out);
+		free(msg_in);
+		return -ENOMEM;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		free(msg_out);
+		free(msg_in);
+		return -EIO;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	free(msg_out);
+	free(msg_in);
+	return 0;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..08d0a48cd9
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..271a85896d
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,82 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Supported Use cases
+-------------------
+ *
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - Dummy command
+     - Dummy command description
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 02/12] app/graph: add telnet connectivity framework
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
  2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16  9:04                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 03/12] app/graph: add parser utility APIs skori
                                         ` (10 subsequent siblings)
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/conn.c           | 282 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 468 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..8c88500605
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 08d0a48cd9..644e5c39f2 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@ endif
 deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 271a85896d..26a7982722 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -41,6 +41,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -74,7 +84,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IPv4 address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 03/12] app/graph: add parser utility APIs
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
  2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
  2023-09-29  9:58                       ` [PATCH v8 02/12] app/graph: add telnet connectivity framework skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16  9:05                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 04/12] app/graph: add mempool command line interfaces skori
                                         ` (9 subsequent siblings)
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index 644e5c39f2..d322f27d8e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,4 +13,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 04/12] app/graph: add mempool command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (2 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 03/12] app/graph: add parser utility APIs skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16  9:09                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 05/12] app/graph: add ethdev " skori
                                         ` (8 subsequent siblings)
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   8 +++
 7 files changed, 211 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 473fa1635a..c9f932517e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..901f07f461
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	strcpy(mpconfig.config[nb_pools].name, config->name);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		64, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	strcpy(config.name, res->name);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d322f27d8e..2027183050 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,5 +13,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 26a7982722..9f6b83e248 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -80,6 +80,14 @@ file to express the requested use case configuration.
      - Dummy command description
      - No
      - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 05/12] app/graph: add ethdev command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (3 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 04/12] app/graph: add mempool command line interfaces skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16 13:20                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 06/12] app/graph: add ipv4_lookup " skori
                                         ` (7 subsequent siblings)
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  47 ++
 8 files changed, 1092 insertions(+)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c9f932517e..c4b5cf3ce1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..74e80679d9
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	uint16_t port_id = 0;
+	int numa_node, rc;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 2027183050..aaaced4932 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9f6b83e248..0d810c2389 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -88,6 +88,42 @@ file to express the requested use case configuration.
      - Command to dump mempool help message
      - Yes
      - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - Yes
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
@@ -121,6 +157,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (4 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 05/12] app/graph: add ethdev " skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16 15:47                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 07/12] app/graph: add ipv6_lookup " skori
                                         ` (6 subsequent siblings)
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index c4b5cf3ce1..430750db6e 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 74e80679d9..4d2bc73e7c 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..db3354c270
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+
+	depth = convert_netmask_to_depth(ipv4route->netmask);
+
+	return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
+			RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+	int rc = -EINVAL;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route4_rewirte_table_update(ipv4route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+free:
+	free(ipv4route);
+	return rc;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		rc = route4_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index aaaced4932..fee22cae5f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 0d810c2389..018df7efde 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -124,6 +124,15 @@ file to express the requested use case configuration.
      - Command to dump ethdev help message
      - Yes
      - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - Yes
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 07/12] app/graph: add ipv6_lookup command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (5 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 06/12] app/graph: add ipv4_lookup " skori
@ 2023-09-29  9:58                       ` skori
  2023-09-29  9:58                       ` [PATCH v8 08/12] app/graph: add neigh " skori
                                         ` (5 subsequent siblings)
  12 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 253 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 430750db6e..7213a91ad2 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4d2bc73e7c..4c70953b99 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..e793cde830
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+	depth = convert_ip6_netmask_to_depth(ipv6route->mask);
+
+	return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
+			RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+	int rc = -EINVAL;
+	int j;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route6_rewirte_table_update(ipv6route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
+	return 0;
+free:
+	free(ipv6route);
+	return rc;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route6, next) {
+		rc = route6_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index fee22cae5f..c3261a2162 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 018df7efde..9a4a9f9191 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -133,6 +133,15 @@ file to express the requested use case configuration.
      - Command to dump ipv4_lookup help message
      - Yes
      - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - Yes
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 08/12] app/graph: add neigh command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (6 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 07/12] app/graph: add ipv6_lookup " skori
@ 2023-09-29  9:58                       ` skori
  2023-09-29  9:58                       ` [PATCH v8 09/12] app/graph: add ethdev_rx " skori
                                         ` (4 subsequent siblings)
  12 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 360 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 +++++
 doc/guides/tools/graph.rst |  12 ++
 8 files changed, 446 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 7213a91ad2..36338d5173 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4c70953b99..b43b16c300 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c3261a2162..39fe0b984f 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,5 +17,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..af69fc8ade
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,360 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
+	if (portid < 0) {
+		printf("Invalid portid found to add neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0)
+		return rc;
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip6_rewrite_add(portid, data, len, portid);
+
+
+}
+
+static int
+ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip4(v4_config->ip, 0);
+	if (portid < 0) {
+		printf("Invalid portid found to add  neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0) {
+		printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+		return rc;
+	}
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip4_rewrite_add(portid, data, len, portid);
+}
+
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+	int rc = -EINVAL;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = ip4_rewrite_node_add(v4_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+free:
+	free(v4_config);
+	return rc;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+	int rc = -EINVAL;
+	int j;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc =  ip6_rewrite_node_add(v6_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+free:
+	free(v6_config);
+	return rc;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	struct neigh_ipv4_config *neigh;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		rc = ip4_rewrite_node_add(neigh);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	struct neigh_ipv6_config *neigh;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+		rc = ip6_rewrite_node_add(neigh);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9a4a9f9191..9ec801aa64 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -142,6 +142,18 @@ file to express the requested use case configuration.
      - Command to dump ipv6_lookup help message
      - Yes
      - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - Yes
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - Yes
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 09/12] app/graph: add ethdev_rx command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (7 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 08/12] app/graph: add neigh " skori
@ 2023-09-29  9:58                       ` skori
  2023-09-29  9:58                       ` [PATCH v8 10/12] app/graph: add graph " skori
                                         ` (3 subsequent siblings)
  12 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 36338d5173..e947f61ee4 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 39fe0b984f..cafab04f70 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 9ec801aa64..f2920ff136 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -154,6 +154,16 @@ file to express the requested use case configuration.
      - Command to dump neigh help message
      - Yes
      - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 10/12] app/graph: add graph command line interfaces
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (8 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 09/12] app/graph: add ethdev_rx " skori
@ 2023-09-29  9:58                       ` skori
  2023-09-29  9:58                       ` [PATCH v8 11/12] app/graph: add CLI option to enable graph stats skori
                                         ` (2 subsequent siblings)
  12 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 550 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  21 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/ip4_route.c      |   5 +-
 app/graph/ip6_route.c      |   5 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/neigh.c          |  10 +-
 doc/guides/tools/graph.rst |  19 +-
 11 files changed, 681 insertions(+), 7 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index e947f61ee4..c43af5925c 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..b27abcf1f9
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,550 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+bool graph_started;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link rc");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				strncpy(graph_config.usecases[j].name, token, 31);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	if (fp == NULL)
+		rte_exit(EXIT_FAILURE, "Errot in opening stats file\n");
+
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+bool
+graph_status_get(void)
+{
+	return graph_started;
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+	int rc = -EINVAL;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+
+	if (!rc)
+		graph_started = true;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= RTE_BIT64(lcore_id);
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..a14fa37ccd
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+bool graph_status_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
index db3354c270..fc83586427 100644
--- a/app/graph/ip4_route.c
+++ b/app/graph/ip4_route.c
@@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
 	ipv4route->via = route->via;
 	ipv4route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route4_rewirte_table_update(ipv4route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
 	return 0;
 free:
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
index e793cde830..1fa4865220 100644
--- a/app/graph/ip6_route.c
+++ b/app/graph/ip6_route.c
@@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
 	}
 	ipv6route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route6_rewirte_table_update(ipv6route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
 	return 0;
 free:
diff --git a/app/graph/meson.build b/app/graph/meson.build
index cafab04f70..c0b59b9a92 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
index af69fc8ade..706be3840f 100644
--- a/app/graph/neigh.c
+++ b/app/graph/neigh.c
@@ -156,11 +156,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
 	v4_config->mac = mac;
 	v4_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = ip4_rewrite_node_add(v4_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
 	return 0;
 free:
@@ -189,11 +192,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
 	v6_config->mac = mac;
 	v6_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc =  ip6_rewrite_node_add(v6_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
 	return 0;
 free:
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index f2920ff136..7d2aa95c95 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -76,10 +76,25 @@ file to express the requested use case configuration.
      - Description
      - Dynamic
      - Optional
-   * - Dummy command
-     - Dummy command description
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+       pcap_enable <0 | 1> num_pcap_pkts <num> pcap_file <output_capture_file>
+     - Command to express the desired use case. Also enables/disable pcap capturing
      - No
      - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
    * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
      - Command to create mempool which will be further associated to RxQ to dequeue the packets
      - No
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 11/12] app/graph: add CLI option to enable graph stats
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (9 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 10/12] app/graph: add graph " skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16 15:55                         ` Jerin Jacob
  2023-09-29  9:58                       ` [PATCH v8 12/12] app/graph: add l3fwd use case skori
  2023-10-16 16:01                       ` [PATCH v8 00/12] add CLI based graph application Jerin Jacob
  12 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

It adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 7d2aa95c95..d548cb67ec 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -57,6 +57,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v8 12/12] app/graph: add l3fwd use case
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (10 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 11/12] app/graph: add CLI option to enable graph stats skori
@ 2023-09-29  9:58                       ` skori
  2023-10-16 16:01                       ` [PATCH v8 00/12] add CLI based graph application Jerin Jacob
  12 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-09-29  9:58 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

It adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  87 ++++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |   9 +-
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 8 files changed, 455 insertions(+), 2 deletions(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..1038fde04e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index b27abcf1f9..bc8cd0fd1e 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -269,7 +269,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				rc  = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c0b59b9a92..27bac23ef0 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index d548cb67ec..5fa4c2a6d5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,7 +12,7 @@ Based on the input file, application creates a graph to cater the use case.
 
 Supported Use cases
 -------------------
- *
+ * l3fwd
 
 Running the Application
 -----------------------
@@ -232,3 +232,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 01/12] app/graph: add application framework to read CLI
  2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
@ 2023-10-16  9:00                         ` Jerin Jacob
  2023-10-17  6:19                           ` [EXT] " Sunil Kumar Kori
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
  1 sibling, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16  9:00 UTC (permalink / raw)
  To: skori; +Cc: Thomas Monjalon, Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 3:28 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds base framework to read a given .cli file as a command line
> parameter "-s".
>
> Example:
>  # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli
>
> Each .cli file will contain commands to configure different module like
> mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
> commandline library.
>
> Each module needs to expose its supported commands & corresponding
> callback functions to commandline library to get them parsed.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>

> +cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
> +{
> +       char *msg_in = NULL, *msg_out = NULL;
> +       FILE *f = NULL;
> +
> +       /* Check input arguments */
> +       if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
> +           (msg_out_len_max == 0))
> +               return -EINVAL;
> +
> +       msg_in = malloc(msg_in_len_max + 1);
> +       msg_out = malloc(msg_out_len_max + 1);
> +       if ((msg_in == NULL) || (msg_out == NULL)) {

Use goto to avod dupliciting this section.

> +               free(msg_out);
> +               free(msg_in);
> +               return -ENOMEM;
> +       }
> +
> +       /* Open input file */
> +       f = fopen(file_name, "r");
> +       if (f == NULL) {
> +               free(msg_out);
> +               free(msg_in);
> +               return -EIO;
> +       }
> +
> +       /* Read file */
> +       while (fgets(msg_in, msg_in_len_max, f) != NULL) {
> +               msg_out[0] = 0;
> +
> +               cli_process(msg_in, msg_out, msg_out_len_max, obj);
> +
> +               if (strlen(msg_out))
> +                       printf("%s", msg_out);
> +       }
> +
> +       /* Close file */
> +       fclose(f);

See above:

> +       free(msg_out);
> +       free(msg_in);
> +       return 0;
> +}
>
new file mode 100644
> index 0000000000..08d0a48cd9
> --- /dev/null
> +++ b/app/graph/meson.build
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 Marvell.
> +
> +# override default name to drop the hyphen
> +name = 'graph'
> +build = cc.has_header('sys/epoll.h')

Is this required?

> +if not build
> +    subdir_done()
> +endif
> +
> +deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']

Check bus_pci really required?

> +sources = files(
> +        'cli.c',
> +        'main.c',
> +)
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> new file mode 100644
> index 0000000000..372aeae7e3
> --- /dev/null
> +++ b/app/graph/module_api.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MODULE_API_H
> +#define APP_GRAPH_MODULE_API_H
> +
> +#include <stdint.h>
> +#include <stdbool.h>

Have a new line here.

> +#include "cli.h"
> +/*
> + * Externs
> + */
> new file mode 100644
> index 0000000000..271a85896d
> --- /dev/null
> +++ b/doc/guides/tools/graph.rst
> @@ -0,0 +1,82 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(c) 2023 Marvell.
> +
> +dpdk-graph Application
> +======================
> +
> +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> +application that allows exercising various graph use cases.
> +This application has a generic framework to add new graph based use cases to
> +verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
> +Based on the input file, application creates a graph to cater the use case.

Could you add some detailed text to describe the fact that, due to
modular nature
this app framework can be used by other graph based application OR SO.

> +
> +Supported Use cases
> +-------------------
> + *

This * can be removed in this patch. Add it when l3fwd adds it.


> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options which can be provided in
> +following syntax
> +
> +.. code-block:: console
> +
> +   dpdk-graph [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +Following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
> +        list of cores to be used.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +Following are the application command-line options:
> +
> +* ``-s``
> +
> +        Script name with absolute path which specifies the use case. It is
> +        a mandatory parameter which will be used to create desired graph
> +        for a given use case.
> +
> +* ``--help``
> +
> +       Dumps application usage
> +
> +Supported CLI commands
> +----------------------
> +
> +This section provides details on commands which can be used in ``<usecase>.cli``
> +file to express the requested use case configuration.
> +
> +.. list-table:: Exposed CLIs
> +   :widths: 40 40 10 10
> +   :header-rows: 1
> +   :class: longtable
> +
> +   * - Command
> +     - Description
> +     - Dynamic
> +     - Optional
> +   * - Dummy command
> +     - Dummy command description
> +     - No
> +     - No

Is it possible to make it as more vertical placement ? More horizontal
placement is not PDF friendly.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 02/12] app/graph: add telnet connectivity framework
  2023-09-29  9:58                       ` [PATCH v8 02/12] app/graph: add telnet connectivity framework skori
@ 2023-10-16  9:04                         ` Jerin Jacob
  2023-10-17  6:21                           ` [EXT] " Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16  9:04 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 10:36 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds framework to initiate a telnet session with application.
>
> Some configurations and debug commands are exposed as runtime APIs.
> Those commands can be invoked using telnet session.
>
> Application initiates a telnet server with host address 0.0.0.0
> and port number 8086 by default.
>
> To make it configurable, "-h" and "-p" options are provided.
> Using them user can pass host address and port number on which
> application will start telnet server.
>
> Using same host address and port number, telnet client can connect
> to application.
>
> Syntax to connect with application:
>         # telnet <host> <port>
>
> Once session is connected, "graph> " prompt will be available.
> Example:
>         # telnet 10.28.35.207 50000
>           Trying 10.28.35.207...
>           Connected to 10.28.35.207.
>           Escape character is '^]'.
>
>           Welcome!
>
>           graph>
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---

>
> +Application allows some configuration to be modified at runtime using a telnet session.
> +Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
> +by default.
> +
> +if user passes ``-h`` and ``-p`` options while running application then corresponding
> +IPv4 address and port number will be used for telnet session.

IPv4 -> IP

> +
> +After successful launch of application, client can connect to application using given
> +host & port and console will be accessed with prompt ``graph>``.

With above changes,

Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 03/12] app/graph: add parser utility APIs
  2023-09-29  9:58                       ` [PATCH v8 03/12] app/graph: add parser utility APIs skori
@ 2023-10-16  9:05                         ` Jerin Jacob
  0 siblings, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16  9:05 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 3:28 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds some helper functions to parse IPv4, IPv6 and MAC addresses
> string into respective datatype.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 04/12] app/graph: add mempool command line interfaces
  2023-09-29  9:58                       ` [PATCH v8 04/12] app/graph: add mempool command line interfaces skori
@ 2023-10-16  9:09                         ` Jerin Jacob
  2023-10-17  6:22                           ` [EXT] " Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16  9:09 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 3:28 PM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> It adds mempool module which will be creating mempools.
>
> Following commands are exposed:
>  - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
>         cache <cache_size> numa <numa_id>
>  - help mempool
>
> User will add this command in .cli file according to its need.

Subject can be changed to: support mempool

Similar theme for other patches too.

>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---

Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 05/12] app/graph: add ethdev command line interfaces
  2023-09-29  9:58                       ` [PATCH v8 05/12] app/graph: add ethdev " skori
@ 2023-10-16 13:20                         ` Jerin Jacob
  2023-10-16 14:10                           ` Bruce Richardson
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16 13:20 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 3:29 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds ethdev module to configure ethernet devices.
>
> Following commands are exposed:
>  - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
>  - ethdev <ethdev_name> mtu <mtu_sz>
>  - ethdev <ethdev_name> promiscuous <on/off>
>  - ethdev <ethdev_name> show
>  - ethdev <ethdev_name> stats
>  - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
>  - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
>  - help ethdev
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---

Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 05/12] app/graph: add ethdev command line interfaces
  2023-10-16 13:20                         ` Jerin Jacob
@ 2023-10-16 14:10                           ` Bruce Richardson
  2023-10-17  6:26                             ` [EXT] " Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Bruce Richardson @ 2023-10-16 14:10 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: skori, Rakesh Kudurumalla, dev

On Mon, Oct 16, 2023 at 06:50:47PM +0530, Jerin Jacob wrote:
> On Fri, Sep 29, 2023 at 3:29 PM <skori@marvell.com> wrote:
> >
> > From: Sunil Kumar Kori <skori@marvell.com>
> >
> > It adds ethdev module to configure ethernet devices.
> >
> > Following commands are exposed:
> >  - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
> >  - ethdev <ethdev_name> mtu <mtu_sz>
> >  - ethdev <ethdev_name> promiscuous <on/off>
> >  - ethdev <ethdev_name> show
> >  - ethdev <ethdev_name> stats
> >  - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> >  - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> >  - help ethdev
> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > ---
> 
> Acked-by: Jerin Jacob <jerinj@marvell.com>

Just FYI - if patchset [1] is merged, you could potentially save a lot of
code additions in this library by autogenerating the boilerplate for the
commandline. Maybe you could take a look and see if it could work for your
usecase here with commandline. If any cmdline features are missing with it,
it could be good to add them.

/Bruce

[1] http://patches.dpdk.org/project/dpdk/list/?series=29852

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-09-29  9:58                       ` [PATCH v8 06/12] app/graph: add ipv4_lookup " skori
@ 2023-10-16 15:47                         ` Jerin Jacob
  2023-10-17  6:30                           ` [EXT] " Sunil Kumar Kori
  0 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16 15:47 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 8:16 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds ipv4_lookup module to configure LPM table. This LPM table
> will be used for IPv4 lookup and forwarding.
>
> Following commands are exposed:
>  - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
>  - help ipv4_lookup
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   2 +
>  app/graph/ethdev.c         |   2 +-
>  app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   1 +
>  app/graph/route.h          |  26 +++++
>  app/graph/route_priv.h     |  44 ++++++++
>  doc/guides/tools/graph.rst |   9 ++
>  8 files changed, 305 insertions(+), 1 deletion(-)
>  create mode 100644 app/graph/ip4_route.c
>  create mode 100644 app/graph/route.h
>  create mode 100644 app/graph/route_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index c4b5cf3ce1..430750db6e 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> index 74e80679d9..4d2bc73e7c 100644
> --- a/app/graph/ethdev.c
> +++ b/app/graph/ethdev.c
> @@ -160,7 +160,7 @@ ethdev_stop(void)
>         }
>
>         ethdev_list_clean();
> -       rte_eal_cleanup();

Looks like by mistake it is removed in this patch.

> +       route_ip4_list_clean();
>         printf("Bye...\n");
>  }

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 11/12] app/graph: add CLI option to enable graph stats
  2023-09-29  9:58                       ` [PATCH v8 11/12] app/graph: add CLI option to enable graph stats skori
@ 2023-10-16 15:55                         ` Jerin Jacob
  0 siblings, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16 15:55 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Fri, Sep 29, 2023 at 3:29 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> It adds application's command line parameter "--enable-graph-stats"
> to enable dumping graph stats on console.
>
> By default, no graph stats will be printed on console but same can
> be dumped via telnet session using "graph stats show" command.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 00/12] add CLI based graph application
  2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
                                         ` (11 preceding siblings ...)
  2023-09-29  9:58                       ` [PATCH v8 12/12] app/graph: add l3fwd use case skori
@ 2023-10-16 16:01                       ` Jerin Jacob
  2023-10-16 16:27                         ` Stephen Hemminger
  12 siblings, 1 reply; 182+ messages in thread
From: Jerin Jacob @ 2023-10-16 16:01 UTC (permalink / raw)
  To: skori; +Cc: dev

On Fri, Sep 29, 2023 at 5:56 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> In the continuation of following feedback
> https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
> this patch series adds dpdk-graph application to exercise various
> usecases using graph.
>
> 1. Each use case is defined in terms of .cli file which will contain
> set of commands to configure the system and to create a graph for
> that use case.
>
> 2. Each module like ethdev, mempool, route etc exposes its set of commands
> to do global and node specific configuration.
>
> 3. Command parsing is backed by command line library.

1) Please update the release notes for this new app.
2) Fix following issues

### [PATCH] app/graph: add telnet connectivity framework

WARNING:STRNCPY: Prefer strscpy, strscpy_pad, or __nonstring over
strncpy - see: https://github.com/KSPP/linux/issues/90
#205: FILE: app/graph/conn.c:162:
+       strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);

WARNING:STRNCPY: Prefer strscpy, strscpy_pad, or __nonstring over
strncpy - see: https://github.com/KSPP/linux/issues/90
#206: FILE: app/graph/conn.c:163:
+       strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);

total: 0 errors, 2 warnings, 554 lines checked

### [PATCH] app/graph: add mempool command line interfaces

WARNING:STRCPY: Prefer strscpy over strcpy - see:
https://github.com/KSPP/linux/issues/88
#68: FILE: app/graph/mempool.c:32:
+       strcpy(mpconfig.config[nb_pools].name, config->name);

WARNING:STRCPY: Prefer strscpy over strcpy - see:
https://github.com/KSPP/linux/issues/88
#110: FILE: app/graph/mempool.c:74:
+       strcpy(config.name, res->name);


### [PATCH] app/graph: add graph command line interfaces

WARNING:STRNCPY: Prefer strscpy, strscpy_pad, or __nonstring over
strncpy - see: https://github.com/KSPP/linux/issues/90
#166: FILE: app/graph/graph.c:113:
+                               strncpy(graph_config.usecases[j].name,
token, 31);

WARNING:TYPO_SPELLING: 'Errot' may be misspelled - perhaps 'Error'?
#269: FILE: app/graph/graph.c:216:
+               rte_exit(EXIT_FAILURE, "Errot in opening stats file\n");
                                        ^^^^^

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v8 00/12] add CLI based graph application
  2023-10-16 16:01                       ` [PATCH v8 00/12] add CLI based graph application Jerin Jacob
@ 2023-10-16 16:27                         ` Stephen Hemminger
  0 siblings, 0 replies; 182+ messages in thread
From: Stephen Hemminger @ 2023-10-16 16:27 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: skori, dev

On Mon, 16 Oct 2023 21:31:29 +0530
Jerin Jacob <jerinjacobk@gmail.com> wrote:

> ### [PATCH] app/graph: add telnet connectivity framework
> 
> WARNING:STRNCPY: Prefer strscpy, strscpy_pad, or __nonstring over
> strncpy - see: https://github.com/KSPP/linux/issues/90
> #205: FILE: app/graph/conn.c:162:
> +       strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> 
> WARNING:STRNCPY: Prefer strscpy, strscpy_pad, or __nonstring over
> strncpy - see: https://github.com/KSPP/linux/issues/90
> #206: FILE: app/graph/conn.c:163:
> +       strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
> 
> total: 0 errors, 2 warnings, 554 lines checked


These are from the kernel version of checkpatch which now is pushing
developers towards replacement kernel apis.

For DPDK though you should be using strlcpy() in most places to avoid
the missing null end of string on copy of full size.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v8 01/12] app/graph: add application framework to read CLI
  2023-10-16  9:00                         ` Jerin Jacob
@ 2023-10-17  6:19                           ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-17  6:19 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: Thomas Monjalon, Rakesh Kudurumalla, dev

> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, October 16, 2023 2:31 PM
> To: Sunil Kumar Kori <skori@marvell.com>
> Cc: Thomas Monjalon <thomas@monjalon.net>; Rakesh Kudurumalla
> <rkudurumalla@marvell.com>; dev@dpdk.org
> Subject: [EXT] Re: [PATCH v8 01/12] app/graph: add application framework
> to read CLI
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Sep 29, 2023 at 3:28 PM <skori@marvell.com> wrote:
> >
> > From: Sunil Kumar Kori <skori@marvell.com>
> >
> > It adds base framework to read a given .cli file as a command line
> > parameter "-s".
> >
> > Example:
> >  # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli
> >
> > Each .cli file will contain commands to configure different module
> > like mempool, ethdev, lookup tables, graph etc. Command parsing is
> > backed by commandline library.
> >
> > Each module needs to expose its supported commands & corresponding
> > callback functions to commandline library to get them parsed.
> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> 
> > +cli_script_process(const char *file_name, size_t msg_in_len_max,
> > +size_t msg_out_len_max, void *obj) {
> > +       char *msg_in = NULL, *msg_out = NULL;
> > +       FILE *f = NULL;
> > +
> > +       /* Check input arguments */
> > +       if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max
> == 0) ||
> > +           (msg_out_len_max == 0))
> > +               return -EINVAL;
> > +
> > +       msg_in = malloc(msg_in_len_max + 1);
> > +       msg_out = malloc(msg_out_len_max + 1);
> > +       if ((msg_in == NULL) || (msg_out == NULL)) {
> 
> Use goto to avod dupliciting this section.
> 
Will fix in next version.

> > +               free(msg_out);
> > +               free(msg_in);
> > +               return -ENOMEM;
> > +       }
> > +
> > +       /* Open input file */
> > +       f = fopen(file_name, "r");
> > +       if (f == NULL) {
> > +               free(msg_out);
> > +               free(msg_in);
> > +               return -EIO;
> > +       }
> > +
> > +       /* Read file */
> > +       while (fgets(msg_in, msg_in_len_max, f) != NULL) {
> > +               msg_out[0] = 0;
> > +
> > +               cli_process(msg_in, msg_out, msg_out_len_max, obj);
> > +
> > +               if (strlen(msg_out))
> > +                       printf("%s", msg_out);
> > +       }
> > +
> > +       /* Close file */
> > +       fclose(f);
> 
> See above:
> 
Will fix in next version.

> > +       free(msg_out);
> > +       free(msg_in);
> > +       return 0;
> > +}
> >
> new file mode 100644
Will fix in next version.

> > index 0000000000..08d0a48cd9
> > --- /dev/null
> > +++ b/app/graph/meson.build
> > @@ -0,0 +1,15 @@
> > +# SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2023 Marvell.
> > +
> > +# override default name to drop the hyphen name = 'graph'
> > +build = cc.has_header('sys/epoll.h')
> 
> Is this required?
> 
Yes. It is needed to compiled  the application for FreeBSD.

> > +if not build
> > +    subdir_done()
> > +endif
> > +
> > +deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node',
> > +'cmdline']
> 
> Check bus_pci really required?
> 
Will check.

> > +sources = files(
> > +        'cli.c',
> > +        'main.c',
> > +)
> > diff --git a/app/graph/module_api.h b/app/graph/module_api.h new file
> > mode 100644 index 0000000000..372aeae7e3
> > --- /dev/null
> > +++ b/app/graph/module_api.h
> > @@ -0,0 +1,16 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(c) 2023 Marvell.
> > + */
> > +
> > +#ifndef APP_GRAPH_MODULE_API_H
> > +#define APP_GRAPH_MODULE_API_H
> > +
> > +#include <stdint.h>
> > +#include <stdbool.h>
> 
> Have a new line here.
> 
Will fix in next version.

> > +#include "cli.h"
> > +/*
> > + * Externs
> > + */
> > new file mode 100644
> > index 0000000000..271a85896d
> > --- /dev/null
> > +++ b/doc/guides/tools/graph.rst
> > @@ -0,0 +1,82 @@
> > +..  SPDX-License-Identifier: BSD-3-Clause
> > +    Copyright(c) 2023 Marvell.
> > +
> > +dpdk-graph Application
> > +======================
> > +
> > +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> > +application that allows exercising various graph use cases.
> > +This application has a generic framework to add new graph based use
> > +cases to verify functionality. Each use case is defined as a ``<usecase>.cli``
> file.
> > +Based on the input file, application creates a graph to cater the use case.
> 
> Could you add some detailed text to describe the fact that, due to modular
> nature this app framework can be used by other graph based application OR
> SO.
> 
> > +
> > +Supported Use cases
> > +-------------------
> > + *
> 
> This * can be removed in this patch. Add it when l3fwd adds it.
> 
Will fix in next version.

> 
> > +
> > +Running the Application
> > +-----------------------
> > +
> > +The application has a number of command line options which can be
> > +provided in following syntax
> > +
> > +.. code-block:: console
> > +
> > +   dpdk-graph [EAL Options] -- [application options]
> > +
> > +EAL Options
> > +~~~~~~~~~~~
> > +
> > +Following are the EAL command-line options that can be used in
> > +conjunction with the ``dpdk-graph`` application.
> > +See the DPDK Getting Started Guides for more information on these
> options.
> > +
> > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > +
> > +        Set the hexadecimal bit mask of the cores to run on. The CORELIST is
> a
> > +        list of cores to be used.
> > +
> > +Application Options
> > +~~~~~~~~~~~~~~~~~~~
> > +
> > +Following are the application command-line options:
> > +
> > +* ``-s``
> > +
> > +        Script name with absolute path which specifies the use case. It is
> > +        a mandatory parameter which will be used to create desired graph
> > +        for a given use case.
> > +
> > +* ``--help``
> > +
> > +       Dumps application usage
> > +
> > +Supported CLI commands
> > +----------------------
> > +
> > +This section provides details on commands which can be used in
> > +``<usecase>.cli`` file to express the requested use case configuration.
> > +
> > +.. list-table:: Exposed CLIs
> > +   :widths: 40 40 10 10
> > +   :header-rows: 1
> > +   :class: longtable
> > +
> > +   * - Command
> > +     - Description
> > +     - Dynamic
> > +     - Optional
> > +   * - Dummy command
> > +     - Dummy command description
> > +     - No
> > +     - No
> 
> Is it possible to make it as more vertical placement ? More horizontal
> placement is not PDF friendly.
Will check it. 

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v8 02/12] app/graph: add telnet connectivity framework
  2023-10-16  9:04                         ` Jerin Jacob
@ 2023-10-17  6:21                           ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-17  6:21 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: Rakesh Kudurumalla, dev

> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, October 16, 2023 2:34 PM
> To: Sunil Kumar Kori <skori@marvell.com>
> Cc: Rakesh Kudurumalla <rkudurumalla@marvell.com>; dev@dpdk.org
> Subject: [EXT] Re: [PATCH v8 02/12] app/graph: add telnet connectivity
> framework
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Sep 29, 2023 at 10:36 PM <skori@marvell.com> wrote:
> >
> > From: Sunil Kumar Kori <skori@marvell.com>
> >
> > It adds framework to initiate a telnet session with application.
> >
> > Some configurations and debug commands are exposed as runtime APIs.
> > Those commands can be invoked using telnet session.
> >
> > Application initiates a telnet server with host address 0.0.0.0 and
> > port number 8086 by default.
> >
> > To make it configurable, "-h" and "-p" options are provided.
> > Using them user can pass host address and port number on which
> > application will start telnet server.
> >
> > Using same host address and port number, telnet client can connect to
> > application.
> >
> > Syntax to connect with application:
> >         # telnet <host> <port>
> >
> > Once session is connected, "graph> " prompt will be available.
> > Example:
> >         # telnet 10.28.35.207 50000
> >           Trying 10.28.35.207...
> >           Connected to 10.28.35.207.
> >           Escape character is '^]'.
> >
> >           Welcome!
> >
> >           graph>
> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > ---
> 
> >
> > +Application allows some configuration to be modified at runtime using a
> telnet session.
> > +Application initiates a telnet server with host address ``0.0.0.0``
> > +and port number ``8086`` by default.
> > +
> > +if user passes ``-h`` and ``-p`` options while running application
> > +then corresponding
> > +IPv4 address and port number will be used for telnet session.
> 
> IPv4 -> IP
> 
Will fix in next version.

> > +
> > +After successful launch of application, client can connect to
> > +application using given host & port and console will be accessed with
> prompt ``graph>``.
> 
> With above changes,
> 
> Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v8 04/12] app/graph: add mempool command line interfaces
  2023-10-16  9:09                         ` Jerin Jacob
@ 2023-10-17  6:22                           ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-17  6:22 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: Rakesh Kudurumalla, dev

> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, October 16, 2023 2:39 PM
> To: Sunil Kumar Kori <skori@marvell.com>
> Cc: Rakesh Kudurumalla <rkudurumalla@marvell.com>; dev@dpdk.org
> Subject: [EXT] Re: [PATCH v8 04/12] app/graph: add mempool command line
> interfaces
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Sep 29, 2023 at 3:28 PM <skori@marvell.com> wrote:
> >
> > From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> >
> > It adds mempool module which will be creating mempools.
> >
> > Following commands are exposed:
> >  - mempool <mempool_name> size <mbuf_size> buffers
> <number_of_buffers> \
> >         cache <cache_size> numa <numa_id>
> >  - help mempool
> >
> > User will add this command in .cli file according to its need.
> 
> Subject can be changed to: support mempool
> 
> Similar theme for other patches too.
> 
Will update the subject.

> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > ---
> 
> Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v8 05/12] app/graph: add ethdev command line interfaces
  2023-10-16 14:10                           ` Bruce Richardson
@ 2023-10-17  6:26                             ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-17  6:26 UTC (permalink / raw)
  To: Bruce Richardson, Jerin Jacob; +Cc: Rakesh Kudurumalla, dev

> -----Original Message-----
> From: Bruce Richardson <bruce.richardson@intel.com>
> Sent: Monday, October 16, 2023 7:41 PM
> To: Jerin Jacob <jerinjacobk@gmail.com>
> Cc: Sunil Kumar Kori <skori@marvell.com>; Rakesh Kudurumalla
> <rkudurumalla@marvell.com>; dev@dpdk.org
> Subject: [EXT] Re: [PATCH v8 05/12] app/graph: add ethdev command line
> interfaces
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Mon, Oct 16, 2023 at 06:50:47PM +0530, Jerin Jacob wrote:
> > On Fri, Sep 29, 2023 at 3:29 PM <skori@marvell.com> wrote:
> > >
> > > From: Sunil Kumar Kori <skori@marvell.com>
> > >
> > > It adds ethdev module to configure ethernet devices.
> > >
> > > Following commands are exposed:
> > >  - ethdev <ethdev_name> rxq <n_queues> txq <n_queues>
> <mempool_name>
> > >  - ethdev <ethdev_name> mtu <mtu_sz>
> > >  - ethdev <ethdev_name> promiscuous <on/off>
> > >  - ethdev <ethdev_name> show
> > >  - ethdev <ethdev_name> stats
> > >  - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> > >  - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> > >  - help ethdev
> > >
> > > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > > ---
> >
> > Acked-by: Jerin Jacob <jerinj@marvell.com>
> 
> Just FYI - if patchset [1] is merged, you could potentially save a lot of code
> additions in this library by autogenerating the boilerplate for the
> commandline. Maybe you could take a look and see if it could work for your
> usecase here with commandline. If any cmdline features are missing with it,
> it could be good to add them.
> 
> /Bruce
> 
> [1] https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__patches.dpdk.org_project_dpdk_list_-3Fseries-
> 3D29852&d=DwIDaQ&c=nKjWec2b6R0mOyPaz7xtfQ&r=dXeXaAMkP5COgn1z
> xHMyaF1_d9IIuq6vHQO6NrIPjaE&m=0QfWbnzd1YwUIe2lmJkzpTQKlyPYvyxa
> qnBpgZRGMKom4bq0WdLuiE1UkZHDdSub&s=AVLoaE4gWQ6VI7Rn61KYZx5cI
> 14JGsmlylXv6NQOrqA&e=

Sure, I will check this after merging the changes. Currently I am planning to get this version merged as it is then later on
once [1] is merged then I will send another patch to incorporate your suggestions.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] Re: [PATCH v8 06/12] app/graph: add ipv4_lookup command line interfaces
  2023-10-16 15:47                         ` Jerin Jacob
@ 2023-10-17  6:30                           ` Sunil Kumar Kori
  0 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-17  6:30 UTC (permalink / raw)
  To: Jerin Jacob; +Cc: Rakesh Kudurumalla, dev

> -----Original Message-----
> From: Jerin Jacob <jerinjacobk@gmail.com>
> Sent: Monday, October 16, 2023 9:18 PM
> To: Sunil Kumar Kori <skori@marvell.com>
> Cc: Rakesh Kudurumalla <rkudurumalla@marvell.com>; dev@dpdk.org
> Subject: [EXT] Re: [PATCH v8 06/12] app/graph: add ipv4_lookup command
> line interfaces
> 
> External Email
> 
> ----------------------------------------------------------------------
> On Fri, Sep 29, 2023 at 8:16 PM <skori@marvell.com> wrote:
> >
> > From: Sunil Kumar Kori <skori@marvell.com>
> >
> > It adds ipv4_lookup module to configure LPM table. This LPM table will
> > be used for IPv4 lookup and forwarding.
> >
> > Following commands are exposed:
> >  - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
> >  - help ipv4_lookup
> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > ---
> >  app/graph/cli.c            |   2 +
> >  app/graph/ethdev.c         |   2 +-
> >  app/graph/ip4_route.c      | 221
> +++++++++++++++++++++++++++++++++++++
> >  app/graph/meson.build      |   1 +
> >  app/graph/module_api.h     |   1 +
> >  app/graph/route.h          |  26 +++++
> >  app/graph/route_priv.h     |  44 ++++++++
> >  doc/guides/tools/graph.rst |   9 ++
> >  8 files changed, 305 insertions(+), 1 deletion(-)  create mode 100644
> > app/graph/ip4_route.c  create mode 100644 app/graph/route.h  create
> > mode 100644 app/graph/route_priv.h
> >
> > diff --git a/app/graph/cli.c b/app/graph/cli.c index
> > c4b5cf3ce1..430750db6e 100644
> > --- a/app/graph/cli.c
> > +++ b/app/graph/cli.c
> > @@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
> >         (cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
> >         (cmdline_parse_inst_t *)&ethdev_cmd_ctx,
> >         (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
> > +       (cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
> > +       (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
> >         NULL,
> >  };
> >
> > diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c index
> > 74e80679d9..4d2bc73e7c 100644
> > --- a/app/graph/ethdev.c
> > +++ b/app/graph/ethdev.c
> > @@ -160,7 +160,7 @@ ethdev_stop(void)
> >         }
> >
> >         ethdev_list_clean();
> > -       rte_eal_cleanup();
> 
> Looks like by mistake it is removed in this patch.
> 
No, It is intentionally moved from here to main() function. This is just to align initialize and cleanup sequence.

> > +       route_ip4_list_clean();
> >         printf("Bye...\n");
> >  }

^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 00/12] add CLI based graph application
  2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
  2023-10-16  9:00                         ` Jerin Jacob
@ 2023-10-18  6:33                         ` skori
  2023-10-18  6:33                           ` [PATCH v9 01/12] app/graph: support application CLI framework skori
                                             ` (11 more replies)
  1 sibling, 12 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: support mempool command line interfaces
  app/graph: support IPv6 lookup command line interfaces
  app/graph: support ethdev Rx command line interfaces
  app/graph: support graph command line interfaces
  app/graph: support l3fwd use case

Sunil Kumar Kori (7):
  app/graph: support application CLI framework
  app/graph: support telnet connectivity framework
  app/graph: support parser utility APIs
  app/graph: support ethdev command line interfaces
  app/graph: support IPv4 lookup command line interfaces
  app/graph: support neigh command line interfaces
  app/graph: support CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 138 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 284 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  87 ++
 app/graph/graph.c                            | 550 ++++++++++++
 app/graph/graph.h                            |  21 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 224 +++++
 app/graph/ip6_route.c                        | 229 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 364 ++++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/rel_notes/release_23_11.rst       |   7 +
 doc/guides/tools/graph.rst                   | 243 +++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 37 files changed, 4750 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 01/12] app/graph: support application CLI framework
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 02/12] app/graph: support telnet connectivity framework skori
                                             ` (10 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v8..v9:
 - Replace strcpy() to rte_strscpy()
 - Update release note.
 - Update user guide

v7..v8:
 - Fix klocwork issues.

v6..v7:
 - Fix FreeBSD build error.
 - Make route and neigh runtime configuration too.

v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                            |   7 ++
 app/graph/cli.c                        | 115 ++++++++++++++++++++++
 app/graph/cli.h                        |  32 +++++++
 app/graph/main.c                       | 128 +++++++++++++++++++++++++
 app/graph/meson.build                  |  15 +++
 app/graph/module_api.h                 |  16 ++++
 app/meson.build                        |   1 +
 doc/guides/rel_notes/release_23_11.rst |   7 ++
 doc/guides/tools/graph.rst             |  80 ++++++++++++++++
 doc/guides/tools/index.rst             |   1 +
 10 files changed, 402 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 80e071d13e..894779f795 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1825,6 +1825,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..df4f8fcbb8
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	int rc = -EINVAL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return rc;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		rc = -EIO;
+		goto exit;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	rc = 0;
+
+exit:
+	free(msg_out);
+	free(msg_in);
+	return rc;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..ed33a04476
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/rel_notes/release_23_11.rst b/doc/guides/rel_notes/release_23_11.rst
index d0c8b8364c..66a55b4bf1 100644
--- a/doc/guides/rel_notes/release_23_11.rst
+++ b/doc/guides/rel_notes/release_23_11.rst
@@ -244,6 +244,13 @@ New Features
   Added dispatcher library which purpose is to help decouple different
   parts (modules) of an eventdev-based application.
 
+* **Added CLI based graph application.**
+
+  Added CLI based graph application which exercises on different usecases.
+  Application provides a framework so that each usecase can be added via CLI
+  file. Each CLI will further be translated into a graph representing user's
+  required usecase.
+
 
 Removed Items
 -------------
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..5c15a6970b
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,80 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Also this application framework can be used by other graph based applications.
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. list-table:: Exposed CLIs
+   :widths: 40 40 10 10
+   :header-rows: 1
+   :class: longtable
+
+   * - Command
+     - Description
+     - Dynamic
+     - Optional
+   * - Dummy command
+     - Dummy command description
+     - No
+     - No
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 02/12] app/graph: support telnet connectivity framework
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
  2023-10-18  6:33                           ` [PATCH v9 01/12] app/graph: support application CLI framework skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 03/12] app/graph: support parser utility APIs skori
                                             ` (9 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 470 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..44934602c7
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <rte_string_fns.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	rte_strscpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed33a04476..c8d2b41b69 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@ endif
 deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 5c15a6970b..cb3f523000 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -39,6 +39,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -72,7 +82,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IP address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 03/12] app/graph: support parser utility APIs
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
  2023-10-18  6:33                           ` [PATCH v9 01/12] app/graph: support application CLI framework skori
  2023-10-18  6:33                           ` [PATCH v9 02/12] app/graph: support telnet connectivity framework skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 04/12] app/graph: support mempool command line interfaces skori
                                             ` (8 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index c8d2b41b69..fd71036a95 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,4 +13,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 04/12] app/graph: support mempool command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (2 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 03/12] app/graph: support parser utility APIs skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 05/12] app/graph: support ethdev " skori
                                             ` (7 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   8 +++
 7 files changed, 211 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index df4f8fcbb8..cf544d5f8f 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..9fd3f8460b
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	rte_strscpy(mpconfig.config[nb_pools].name, config->name, RTE_MEMPOOL_NAMESIZE);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		128, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	rte_strscpy(config.name, res->name, RTE_MEMPOOL_NAMESIZE);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index fd71036a95..5dc23c875b 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,5 +13,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index cb3f523000..ee5b3d48a5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -78,6 +78,14 @@ file to express the requested use case configuration.
      - Dummy command description
      - No
      - No
+   * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
+     - Command to create mempool which will be further associated to RxQ to dequeue the packets
+     - No
+     - No
+   * - help mempool
+     - Command to dump mempool help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 05/12] app/graph: support ethdev command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (3 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 04/12] app/graph: support mempool command line interfaces skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 06/12] app/graph: support IPv4 lookup " skori
                                             ` (6 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  47 ++
 8 files changed, 1092 insertions(+)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index cf544d5f8f..fa394fade6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..74e80679d9
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	uint16_t port_id = 0;
+	int numa_node, rc;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 5dc23c875b..c17e0cc63e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index ee5b3d48a5..adf15b8e59 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -86,6 +86,42 @@ file to express the requested use case configuration.
      - Command to dump mempool help message
      - Yes
      - Yes
+   * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+     - Command to create DPDK port with given number of Rx and Tx queues. Also attached
+       RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
+       share the same mempool.
+     - No
+     - No
+   * - ethdev <ethdev_name> mtu <mtu_sz>
+     - Command to configure MTU of DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> promiscuous <on/off>
+     - Command to enable/disable promiscuous mode on DPDK port
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> show
+     - Command to dump current ethdev configuration
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> stats
+     - Command to dump current ethdev statistics
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+     - Command to configure IPv4 address on given PCI device. It is needed if user
+       wishes to use ``ipv4_lookup`` node
+     - Yes
+     - Yes
+   * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+     - Command to configure IPv6 address on given PCI device. It is needed if user
+       wishes to use ``ipv6_lookup`` node
+     - Yes
+     - Yes
+   * - help ethdev
+     - Command to dump ethdev help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
@@ -119,6 +155,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 06/12] app/graph: support IPv4 lookup command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (4 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 05/12] app/graph: support ethdev " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 07/12] app/graph: support IPv6 " skori
                                             ` (5 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index fa394fade6..25785ea4dc 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 74e80679d9..4d2bc73e7c 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..db3354c270
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+
+	depth = convert_netmask_to_depth(ipv4route->netmask);
+
+	return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
+			RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+	int rc = -EINVAL;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route4_rewirte_table_update(ipv4route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+free:
+	free(ipv4route);
+	return rc;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		rc = route4_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c17e0cc63e..1f35f82583 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index adf15b8e59..333e46654e 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -122,6 +122,15 @@ file to express the requested use case configuration.
      - Command to dump ethdev help message
      - Yes
      - Yes
+   * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM lookup table.
+     - Yes
+     - Yes
+   * - help ipv4_lookup
+     - Command to dump ipv4_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 07/12] app/graph: support IPv6 lookup command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (5 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 06/12] app/graph: support IPv4 lookup " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 08/12] app/graph: support neigh " skori
                                             ` (4 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 253 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 25785ea4dc..1280422388 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4d2bc73e7c..4c70953b99 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..e793cde830
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+	depth = convert_ip6_netmask_to_depth(ipv6route->mask);
+
+	return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
+			RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+	int rc = -EINVAL;
+	int j;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route6_rewirte_table_update(ipv6route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
+	return 0;
+free:
+	free(ipv6route);
+	return rc;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route6, next) {
+		rc = route6_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 1f35f82583..413bbefc4e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 333e46654e..4d4357f202 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -131,6 +131,15 @@ file to express the requested use case configuration.
      - Command to dump ipv4_lookup help message
      - Yes
      - Yes
+   * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
+     - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
+       wishes to route the packets based on LPM6 lookup table.
+     - Yes
+     - Yes
+   * - help ipv6_lookup
+     - Command to dump ipv6_lookup help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 08/12] app/graph: support neigh command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (6 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 07/12] app/graph: support IPv6 " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 09/12] app/graph: support ethdev Rx " skori
                                             ` (3 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 358 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 +++++
 doc/guides/tools/graph.rst |  12 ++
 8 files changed, 444 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 1280422388..f564362da1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4c70953b99..b43b16c300 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 413bbefc4e..8fa9d605b9 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,5 +17,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..0cee502719
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
+	if (portid < 0) {
+		printf("Invalid portid found to add neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0)
+		return rc;
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip6_rewrite_add(portid, data, len, portid);
+}
+
+static int
+ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip4(v4_config->ip, 0);
+	if (portid < 0) {
+		printf("Invalid portid found to add  neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0) {
+		printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+		return rc;
+	}
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip4_rewrite_add(portid, data, len, portid);
+}
+
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+	int rc = -EINVAL;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = ip4_rewrite_node_add(v4_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+free:
+	free(v4_config);
+	return rc;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+	int rc = -EINVAL;
+	int j;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc =  ip6_rewrite_node_add(v6_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+free:
+	free(v6_config);
+	return rc;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	struct neigh_ipv4_config *neigh;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		rc = ip4_rewrite_node_add(neigh);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	struct neigh_ipv6_config *neigh;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+		rc = ip6_rewrite_node_add(neigh);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 4d4357f202..65aa42f500 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -140,6 +140,18 @@ file to express the requested use case configuration.
      - Command to dump ipv6_lookup help message
      - Yes
      - Yes
+   * - neigh add ipv4 <ip> <mac>
+     - Command to add a neighbour information into ``ipv4_rewrite`` node.
+     - Yes
+     - Yes
+   * - neigh add ipv6 <ip> <mac>
+     - Command to add a neighbour information into ``ipv6_rewrite`` node.
+     - Yes
+     - Yes
+   * - help neigh
+     - Command to dump neigh help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 09/12] app/graph: support ethdev Rx command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (7 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 08/12] app/graph: support neigh " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 10/12] app/graph: support graph " skori
                                             ` (2 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index f564362da1..ad7d7deadf 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 8fa9d605b9..d8391d5cae 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 65aa42f500..cf738b7a39 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -152,6 +152,16 @@ file to express the requested use case configuration.
      - Command to dump neigh help message
      - Yes
      - Yes
+   * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
+     - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
+       node instance will be pinned on given core and will poll on requested
+       port/queue pair.
+     - No
+     - No
+   * - help ethdev_rx
+     - Command to dump ethdev_rx help message
+     - Yes
+     - Yes
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 10/12] app/graph: support graph command line interfaces
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (8 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 09/12] app/graph: support ethdev Rx " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 11/12] app/graph: support CLI option to enable graph stats skori
  2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 550 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  21 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/ip4_route.c      |   5 +-
 app/graph/ip6_route.c      |   5 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/neigh.c          |  10 +-
 doc/guides/tools/graph.rst |  19 +-
 11 files changed, 681 insertions(+), 7 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index ad7d7deadf..30b12312d6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..4396f02da5
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,550 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+bool graph_started;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link status...");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				rte_strscpy(graph_config.usecases[j].name, token, 31);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	if (fp == NULL)
+		rte_exit(EXIT_FAILURE, "Errot in opening stats file\n");
+
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+bool
+graph_status_get(void)
+{
+	return graph_started;
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+	int rc = -EINVAL;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+
+	if (!rc)
+		graph_started = true;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= RTE_BIT64(lcore_id);
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..a14fa37ccd
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+bool graph_status_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
index db3354c270..fc83586427 100644
--- a/app/graph/ip4_route.c
+++ b/app/graph/ip4_route.c
@@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
 	ipv4route->via = route->via;
 	ipv4route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route4_rewirte_table_update(ipv4route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
 	return 0;
 free:
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
index e793cde830..1fa4865220 100644
--- a/app/graph/ip6_route.c
+++ b/app/graph/ip6_route.c
@@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
 	}
 	ipv6route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route6_rewirte_table_update(ipv6route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
 	return 0;
 free:
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d8391d5cae..15d16a302e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
index 0cee502719..22be7361e3 100644
--- a/app/graph/neigh.c
+++ b/app/graph/neigh.c
@@ -154,11 +154,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
 	v4_config->mac = mac;
 	v4_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = ip4_rewrite_node_add(v4_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
 	return 0;
 free:
@@ -187,11 +190,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
 	v6_config->mac = mac;
 	v6_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc =  ip6_rewrite_node_add(v6_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
 	return 0;
 free:
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index cf738b7a39..c27c2f1be3 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -74,10 +74,25 @@ file to express the requested use case configuration.
      - Description
      - Dynamic
      - Optional
-   * - Dummy command
-     - Dummy command description
+   * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd | default>
+       pcap_enable <0 | 1> num_pcap_pkts <num> pcap_file <output_capture_file>
+     - Command to express the desired use case. Also enables/disable pcap capturing
      - No
      - No
+   * - graph start
+     - Command to start the graph.
+       This command triggers that no more commands are left to be parsed and graph
+       initialization can be started now. It must be the last command in ``<usecase>.cli``
+     - No
+     - No
+   * - graph stats show
+     - Command to dump current graph statistics
+     - Yes
+     - Yes
+   * - help graph
+     - Command to dump graph help message
+     - Yes
+     - Yes
    * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache <cache_size> numa <numa_id>
      - Command to create mempool which will be further associated to RxQ to dequeue the packets
      - No
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 11/12] app/graph: support CLI option to enable graph stats
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (9 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 10/12] app/graph: support graph " skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index c27c2f1be3..ed0fdfffe1 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -55,6 +55,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v9 12/12] app/graph: support l3fwd use case
  2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
                                             ` (10 preceding siblings ...)
  2023-10-18  6:33                           ` [PATCH v9 11/12] app/graph: support CLI option to enable graph stats skori
@ 2023-10-18  6:33                           ` skori
  2023-10-18 10:38                             ` Jerin Jacob
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
  11 siblings, 2 replies; 182+ messages in thread
From: skori @ 2023-10-18  6:33 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  87 ++++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |  11 +
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 8 files changed, 458 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..1038fde04e
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,87 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:04:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:05:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:06:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:07:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:04:00.0 mtu 1700
+ethdev 0002:05:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
+ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
+ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
+ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
+neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
+neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:04:00.0 queue 0 core 1
+ethdev_rx map port 0002:05:00.0 queue 0 core 2
+ethdev_rx map port 0002:06:00.0 queue 0 core 3
+ethdev_rx map port 0002:07:00.0 queue 0 core 4
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index 4396f02da5..496105eef1 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -269,7 +269,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				rc  = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 15d16a302e..5b0f966d99 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index ed0fdfffe1..fc62e53ae1 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,6 +12,10 @@ Based on the input file, application creates a graph to cater the use case.
 
 Also this application framework can be used by other graph based applications.
 
+Supported Use cases
+-------------------
+ * l3fwd
+
 Running the Application
 -----------------------
 
@@ -230,3 +234,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v9 12/12] app/graph: support l3fwd use case
  2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
@ 2023-10-18 10:38                             ` Jerin Jacob
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
  1 sibling, 0 replies; 182+ messages in thread
From: Jerin Jacob @ 2023-10-18 10:38 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

On Wed, Oct 18, 2023 at 12:05 PM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> Adds an use case l3fwd. It contains a dedicated l3fwd.cli file
> mentioning commands to configure the required resources.
>
> Once application successfully parses the l3fwd.cli then a graph is
> created having below nodes:
>  - ethdev_rx -> pkt_cls
>
>  - pkt_cls -> ip4_lookup
>  - pkt_cls -> ip6_lookup
>  - pkt_cls -> pkt_drop
>
>  - ip4_lookup -> ip4_rewrite
>  - ip4_lookup -> pkt_drop
>
>  - ip6_lookup -> ip6_rewrite
>  - ip6_lookup -> pkt_drop
>
>  - ip4_rewrite -> ethdev_tx
>  - ip4_rewrite -> pkt_drop
>
>  - ip6_rewrite -> ethdev_tx
>  - ip6_rewrite -> pkt_drop
>
>  - ethdev_tx -> pkt_drop
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>

> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index ed0fdfffe1..fc62e53ae1 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -12,6 +12,10 @@ Based on the input file, application creates a graph to cater the use case.
>
>  Also this application framework can be used by other graph based applications.
>
> +Supported Use cases
> +-------------------
> + * l3fwd

Please add commands, .cli file and pcap generation recipe to test this
with just ring pmd + pcap file.(i.e without HW) for others to easily
use and verify it.

^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 00/12] add CLI based graph application
  2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
  2023-10-18 10:38                             ` Jerin Jacob
@ 2023-10-19 10:49                             ` skori
  2023-10-19 10:49                               ` [PATCH v10 01/12] app/graph: support application CLI framework skori
                                                 ` (11 more replies)
  1 sibling, 12 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: support mempool command line interfaces
  app/graph: support IPv6 lookup command line interfaces
  app/graph: support ethdev Rx command line interfaces
  app/graph: support graph command line interfaces
  app/graph: support l3fwd use case

Sunil Kumar Kori (7):
  app/graph: support application CLI framework
  app/graph: support telnet connectivity framework
  app/graph: support parser utility APIs
  app/graph: support ethdev command line interfaces
  app/graph: support IPv4 lookup command line interfaces
  app/graph: support neigh command line interfaces
  app/graph: support CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 138 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 284 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 885 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  73 ++
 app/graph/examples/l3fwd_pcap.cli            |  71 ++
 app/graph/graph.c                            | 550 ++++++++++++
 app/graph/graph.h                            |  21 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 224 +++++
 app/graph/ip6_route.c                        | 229 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 364 ++++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/rel_notes/release_23_11.rst       |   7 +
 doc/guides/tools/graph.rst                   | 334 +++++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 38 files changed, 4898 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/examples/l3fwd_pcap.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 01/12] app/graph: support application CLI framework
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 02/12] app/graph: support telnet connectivity framework skori
                                                 ` (10 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v9..v10
 - Add l3fwd_pcap.cli for pcap devices.
 - Update table generation mechanism user guide document

v8..v9:
 - Replace strcpy() to rte_strscpy()
 - Update release note.
 - Update user guide

v7..v8:
 - Fix klocwork issues.

v6..v7:
 - Fix FreeBSD build error.
 - Make route and neigh runtime configuration too.

v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                            |   7 ++
 app/graph/cli.c                        | 115 ++++++++++++++++++++++
 app/graph/cli.h                        |  32 +++++++
 app/graph/main.c                       | 128 +++++++++++++++++++++++++
 app/graph/meson.build                  |  15 +++
 app/graph/module_api.h                 |  16 ++++
 app/meson.build                        |   1 +
 doc/guides/rel_notes/release_23_11.rst |   7 ++
 doc/guides/tools/graph.rst             |  75 +++++++++++++++
 doc/guides/tools/index.rst             |   1 +
 10 files changed, 397 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 4083658697..88a71d5455 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1818,6 +1818,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..df4f8fcbb8
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	int rc = -EINVAL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return rc;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		rc = -EIO;
+		goto exit;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	rc = 0;
+
+exit:
+	free(msg_out);
+	free(msg_in);
+	return rc;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..ed33a04476
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/rel_notes/release_23_11.rst b/doc/guides/rel_notes/release_23_11.rst
index 0a6fc76a9d..f43b416565 100644
--- a/doc/guides/rel_notes/release_23_11.rst
+++ b/doc/guides/rel_notes/release_23_11.rst
@@ -243,6 +243,13 @@ New Features
   Added dispatcher library which purpose is to help decouple different
   parts (modules) of an eventdev-based application.
 
+* **Added CLI based graph application.**
+
+  Added CLI based graph application which exercises on different usecases.
+  Application provides a framework so that each usecase can be added via CLI
+  file. Each CLI will further be translated into a graph representing user's
+  required usecase.
+
 
 Removed Items
 -------------
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..cb005e7856
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,75 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Also this application framework can be used by other graph based applications.
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. table:: Exposed CLIs
+   :widths: auto
+
+   +--------------------------------------+-----------------------------------+---------+----------+
+   |               Command                |             Description           | Dynamic | Optional |
+   +======================================+===================================+=========+==========+
+   |                                      |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 02/12] app/graph: support telnet connectivity framework
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
  2023-10-19 10:49                               ` [PATCH v10 01/12] app/graph: support application CLI framework skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 03/12] app/graph: support parser utility APIs skori
                                                 ` (9 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 470 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..44934602c7
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <rte_string_fns.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	rte_strscpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed33a04476..c8d2b41b69 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@ endif
 deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index cb005e7856..943f915049 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -39,6 +39,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -67,7 +77,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IP address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 03/12] app/graph: support parser utility APIs
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
  2023-10-19 10:49                               ` [PATCH v10 01/12] app/graph: support application CLI framework skori
  2023-10-19 10:49                               ` [PATCH v10 02/12] app/graph: support telnet connectivity framework skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 04/12] app/graph: support mempool command line interfaces skori
                                                 ` (8 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index c8d2b41b69..fd71036a95 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,4 +13,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 04/12] app/graph: support mempool command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (2 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 03/12] app/graph: support parser utility APIs skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 05/12] app/graph: support ethdev " skori
                                                 ` (7 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   7 ++
 7 files changed, 210 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index df4f8fcbb8..cf544d5f8f 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..9fd3f8460b
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	rte_strscpy(mpconfig.config[nb_pools].name, config->name, RTE_MEMPOOL_NAMESIZE);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		128, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	rte_strscpy(config.name, res->name, RTE_MEMPOOL_NAMESIZE);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index fd71036a95..5dc23c875b 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,5 +13,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 943f915049..6009b0c291 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -71,6 +71,13 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    |               Command                |             Description           | Dynamic | Optional |
    +======================================+===================================+=========+==========+
+   | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
+   | | <mbuf_size> buffers                | | will be further associated to   |         |          |
+   | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
+   | | cache <cache_size> numa <numa_id>  |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
    |                                      |                                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 05/12] app/graph: support ethdev command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (3 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 04/12] app/graph: support mempool command line interfaces skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 06/12] app/graph: support IPv4 lookup " skori
                                                 ` (6 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 882 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  45 +-
 8 files changed, 1089 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index cf544d5f8f..fa394fade6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..74e80679d9
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	uint16_t port_id = 0;
+	int numa_node, rc;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 5dc23c875b..c17e0cc63e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 6009b0c291..124c16e546 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -78,7 +78,39 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
-   |                                      |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> rxq <n_queues>| | Command to create DPDK port with|   No    |    No    |
+   | | txq <n_queues> <mempool_name>      | | given number of Rx and Tx queues|         |          |
+   |                                      | | . Also attach RxQ with given    |         |          |
+   |                                      | | mempool. Each port can have     |         |          |
+   |                                      | | single mempool only i.e. all    |         |          |
+   |                                      | | RxQs will share the same mempool|         |          |
+   |                                      | | .                               |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> mtu <mtu_sz>    | | Command to configure MTU of DPDK|   Yes   |    Yes   |
+   |                                      | | port.                           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   |  | ethdev <ethdev_name> promiscuous  | | Command to enable/disable       |   Yes   |    Yes   |
+   |  | <on/off>                          | | promiscuous mode on DPDK port.  |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> show            | | Command to dump current ethdev  |   Yes   |    Yes   |
+   |                                      | | configuration.                  |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> stats           | | Command to dump current ethdev  |   Yes   |    Yes   |
+   |                                      | | statistics.                     |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> ip4 addr add  | | Command to configure IPv4       |   Yes   |    Yes   |
+   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
+   |                                      | | is needed if user wishes to use |         |          |
+   |                                      | | ``ipv4_lookup`` node.           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> ip6 addr add  | | Command to configure IPv6       |   Yes   |    Yes   |
+   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
+   |                                      | | is needed if user wishes to use |         |          |
+   |                                      | | ``ipv6_lookup`` node.           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
@@ -113,6 +145,17 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> show
+   graph>
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 06/12] app/graph: support IPv4 lookup command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (4 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 05/12] app/graph: support ethdev " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 07/12] app/graph: support IPv6 " skori
                                                 ` (5 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index fa394fade6..25785ea4dc 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 74e80679d9..4d2bc73e7c 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -160,7 +160,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..db3354c270
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+
+	depth = convert_netmask_to_depth(ipv4route->netmask);
+
+	return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
+			RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+	int rc = -EINVAL;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route4_rewirte_table_update(ipv4route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+free:
+	free(ipv4route);
+	return rc;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		rc = route4_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c17e0cc63e..1f35f82583 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 124c16e546..d5f86575b9 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -112,6 +112,15 @@ file to express the requested use case configuration.
    | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ipv4_lookup route add ipv4 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
+   | |  netmask <mask> via <ip>           | | ``ipv4_lookup`` LPM table. It is|         |          |
+   |                                      | | needed if user wishes to route  |         |          |
+   |                                      | | the packets based on LPM lookup |         |          |
+   |                                      | | table.                          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
+   |                                      | | help message.                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 07/12] app/graph: support IPv6 lookup command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (5 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 06/12] app/graph: support IPv4 lookup " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 08/12] app/graph: support neigh " skori
                                                 ` (4 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 253 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 25785ea4dc..1280422388 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4d2bc73e7c..4c70953b99 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -161,6 +161,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..e793cde830
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+	depth = convert_ip6_netmask_to_depth(ipv6route->mask);
+
+	return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
+			RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+	int rc = -EINVAL;
+	int j;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route6_rewirte_table_update(ipv6route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
+	return 0;
+free:
+	free(ipv6route);
+	return rc;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route6, next) {
+		rc = route6_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 1f35f82583..413bbefc4e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index d5f86575b9..6405f1fde6 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -121,6 +121,15 @@ file to express the requested use case configuration.
    | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
    |                                      | | help message.                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ipv6_lookup route add ipv6 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
+   | |  netmask <mask> via <ip>           | | ``ipv6_lookup`` LPM table. It is|         |          |
+   |                                      | | needed if user wishes to route  |         |          |
+   |                                      | | the packets based on LPM6 lookup|         |          |
+   |                                      | | table.                          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
+   |                                      | | help message.                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 08/12] app/graph: support neigh command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (6 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 07/12] app/graph: support IPv6 " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 09/12] app/graph: support ethdev Rx " skori
                                                 ` (3 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 358 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 +++++
 doc/guides/tools/graph.rst |  11 ++
 8 files changed, 443 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 1280422388..f564362da1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4c70953b99..b43b16c300 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -162,6 +162,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 413bbefc4e..8fa9d605b9 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,5 +17,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..0cee502719
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
+	if (portid < 0) {
+		printf("Invalid portid found to add neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0)
+		return rc;
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip6_rewrite_add(portid, data, len, portid);
+}
+
+static int
+ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip4(v4_config->ip, 0);
+	if (portid < 0) {
+		printf("Invalid portid found to add  neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0) {
+		printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+		return rc;
+	}
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip4_rewrite_add(portid, data, len, portid);
+}
+
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+	int rc = -EINVAL;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = ip4_rewrite_node_add(v4_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+free:
+	free(v4_config);
+	return rc;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+	int rc = -EINVAL;
+	int j;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc =  ip6_rewrite_node_add(v6_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+free:
+	free(v6_config);
+	return rc;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	struct neigh_ipv4_config *neigh;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		rc = ip4_rewrite_node_add(neigh);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	struct neigh_ipv6_config *neigh;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+		rc = ip6_rewrite_node_add(neigh);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 6405f1fde6..7c12cf697e 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -130,6 +130,17 @@ file to express the requested use case configuration.
    | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
    |                                      | | help message.                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | neigh add ipv4 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
+   |                                      | | information into                |         |          |
+   |                                      | | ``ipv4_rewrite`` node.          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | neigh add ipv6 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
+   |                                      | | information into                |         |          |
+   |                                      | | ``ipv6_rewrite`` node.          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 09/12] app/graph: support ethdev Rx command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (7 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 08/12] app/graph: support neigh " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 10/12] app/graph: support graph " skori
                                                 ` (2 subsequent siblings)
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index f564362da1..ad7d7deadf 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 8fa9d605b9..d8391d5cae 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 7c12cf697e..5c69a568d1 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -141,6 +141,16 @@ file to express the requested use case configuration.
    | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev_rx map port <ethdev_name>   | | Command to add port-queue-core  |   No    |    No    |
+   | | queue <q_num> core <core_id>       | | mapping to ``ethdev_rx`` node.  |         |          |
+   |                                      | | ``ethdev_rx`` node instance will|         |          |
+   |                                      | | be pinned on given core and will|         |          |
+   |                                      | | poll on requested port/queue    |         |          |
+   |                                      | | pair.                           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ethdev_rx                       | | Command to dump ethdev_rx help  |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 10/12] app/graph: support graph command line interfaces
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (8 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 09/12] app/graph: support ethdev Rx " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:49                               ` [PATCH v10 11/12] app/graph: support CLI option to enable graph stats skori
  2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 550 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  21 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/ip4_route.c      |   5 +-
 app/graph/ip6_route.c      |   5 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/neigh.c          |  10 +-
 doc/guides/tools/graph.rst |  19 ++
 11 files changed, 683 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index ad7d7deadf..30b12312d6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..4396f02da5
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,550 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+bool graph_started;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link status...");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				rte_strscpy(graph_config.usecases[j].name, token, 31);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	if (fp == NULL)
+		rte_exit(EXIT_FAILURE, "Errot in opening stats file\n");
+
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+bool
+graph_status_get(void)
+{
+	return graph_started;
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+	int rc = -EINVAL;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+
+	if (!rc)
+		graph_started = true;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= RTE_BIT64(lcore_id);
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..a14fa37ccd
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+bool graph_status_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
index db3354c270..fc83586427 100644
--- a/app/graph/ip4_route.c
+++ b/app/graph/ip4_route.c
@@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
 	ipv4route->via = route->via;
 	ipv4route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route4_rewirte_table_update(ipv4route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
 	return 0;
 free:
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
index e793cde830..1fa4865220 100644
--- a/app/graph/ip6_route.c
+++ b/app/graph/ip6_route.c
@@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
 	}
 	ipv6route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route6_rewirte_table_update(ipv6route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
 	return 0;
 free:
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d8391d5cae..15d16a302e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
index 0cee502719..22be7361e3 100644
--- a/app/graph/neigh.c
+++ b/app/graph/neigh.c
@@ -154,11 +154,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
 	v4_config->mac = mac;
 	v4_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = ip4_rewrite_node_add(v4_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
 	return 0;
 free:
@@ -187,11 +190,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
 	v6_config->mac = mac;
 	v6_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc =  ip6_rewrite_node_add(v6_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
 	return 0;
 free:
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 5c69a568d1..c861ae8eb5 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -71,6 +71,25 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    |               Command                |             Description           | Dynamic | Optional |
    +======================================+===================================+=========+==========+
+   | | graph <usecases> [bsz <size>]      | | Command to express the desired  |   No    |    No    |
+   | | [tmo <ns>] [coremask <bitmask>]    | | use case. Also enables/disable  |         |          |
+   | | model <rtc/mcd/default> pcap_enable| | pcap capturing.                 |         |          |
+   | | <0/1> num_pcap_pkts <num> pcap_file|                                   |         |          |
+   | | <output_capture_file>              |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | graph start                          | | Command to start the graph.     |   No    |    No    |
+   |                                      | | This command triggers that no   |         |          |
+   |                                      | | more commands are left to be    |         |          |
+   |                                      | | parsed and graph initialization |         |          |
+   |                                      | | can be started now. It must be  |         |          |
+   |                                      | | the last command in usecase.cli |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | graph stats show                     | | Command to dump current graph   |   Yes   |    Yes   |
+   |                                      | | statistics.                     |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help graph                           | | Command to dump graph help      |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
    | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
    | | <mbuf_size> buffers                | | will be further associated to   |         |          |
    | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 11/12] app/graph: support CLI option to enable graph stats
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (9 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 10/12] app/graph: support graph " skori
@ 2023-10-19 10:49                               ` skori
  2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
  11 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:49 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index c861ae8eb5..0be4a09a9a 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -55,6 +55,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v10 12/12] app/graph: support l3fwd use case
  2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
                                                 ` (10 preceding siblings ...)
  2023-10-19 10:49                               ` [PATCH v10 11/12] app/graph: support CLI option to enable graph stats skori
@ 2023-10-19 10:50                               ` skori
  2023-10-19 12:28                                 ` [EXT] " Jerin Jacob Kollanukkaran
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
  11 siblings, 2 replies; 182+ messages in thread
From: skori @ 2023-10-19 10:50 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  73 +++++++
 app/graph/examples/l3fwd_pcap.cli            |  71 +++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   | 109 ++++++++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 9 files changed, 613 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/examples/l3fwd_pcap.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..c4977d4322
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,73 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:02:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:03:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:02:00.0 mtu 1700
+ethdev 0002:03:00.0 mtu 1700
+ethdev 0002:02:00.0 promiscuous on
+ethdev 0002:03:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:02:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:03:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:02:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:03:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:02:00.0 queue 0 core 1
+ethdev_rx map port 0002:03:00.0 queue 0 core 2
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/examples/l3fwd_pcap.cli b/app/graph/examples/l3fwd_pcap.cli
new file mode 100644
index 0000000000..30dde74a65
--- /dev/null
+++ b/app/graph/examples/l3fwd_pcap.cli
@@ -0,0 +1,71 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0x03 bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev net_pcap0 rxq 1 txq 1 mempool0
+ethdev net_pcap1 rxq 1 txq 1 mempool0
+ethdev net_pcap0 promiscuous on
+ethdev net_pcap1 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev net_pcap0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev net_pcap1 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev net_pcap0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev net_pcap1 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port net_pcap0 queue 0 core 1
+ethdev_rx map port net_pcap1 queue 0 core 1
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index 4396f02da5..496105eef1 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -269,7 +269,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				rc  = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 15d16a302e..5b0f966d99 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 0be4a09a9a..2fd150052c 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -12,6 +12,14 @@ Based on the input file, application creates a graph to cater the use case.
 
 Also this application framework can be used by other graph based applications.
 
+Supported Use cases
+-------------------
+ * l3fwd
+
+This use case is supported for both PF and PCAP network devices. To demonstrate,
+corresponding .cli files are available at ``<dpdk_root_dir/app/graph/examples/>``
+named as ``l3fwd.cli`` and  ``l3fwd_pcap.cli`` respectively.
+
 Running the Application
 -----------------------
 
@@ -63,6 +71,26 @@ Following are the application command-line options:
 
        Dumps application usage
 
+Examples
+~~~~~~~~
+
+For PF devices
+
+.. code-block:: console
+
+   ./dpdk-graph -c 0xff -a 0002:02:00.0 -a 0002:03:00.0 --
+             -s <dpdk_root_dir>/app/graph/examples/l3fwd.cli
+
+For net_pcapX devices
+
+.. code-block:: console
+
+   ./dpdk-graph -c 0xff --vdev=net_pcap0,rx_pcap=in_net_pcap0.pcap,tx_pcap=out_net_pcap1.pcap
+                        --vdev=net_pcap1,rx_pcap=in_net_pcap1.pcap,tx_pcap=out_net_pcap0.pcap
+                        -- -s <dpdk_root_dir>/app/graph/examples/l3fwd_pcap.cli
+
+Refer section :ref:`verifying_traffic` to create .pcap file used here.
+
 Supported CLI commands
 ----------------------
 
@@ -223,3 +251,84 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
+
+.. _verifying_traffic:
+
+Verifying traffic
+~~~~~~~~~~~~~~~~~
+
+``l3fwd.cli`` and ``l3fwd_pcap.cli`` creates setup with two network ports. Routing between
+these ports are done by lookup node routing information. For current use case, following
+routing table is used:
+
+.. code-block:: console
+
+   DIP        port
+   10.0.2.2    1
+   20.0.2.2    0
+
+On the successful execution of ``l3fwd.cli`` or ``l3fwd_pcap.cli``, user needs to send traffic
+with mentioned DIP.
+
+For net_pcapX devices, required pcap file should be created and passed to application. These
+pcap files can be created in several ways. Scapy is one of the method to get the same:
+
+.. code-block:: console
+
+   # scapy
+
+                     aSPY//YASa
+             apyyyyCY//////////YCa       |
+            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
+    ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.3
+    AYAsAYYYYYYYY///Ps              cY//S   |
+         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
+         SPPPP///a          pP///AC//Y   |
+              A//A            cyP////C   | Have fun!
+              p///Ac            sC///a   |
+              P////YCpc           A//A   | We are in France, we say Skappee.
+       scccccp///pSP///p          p//Y   | OK? Merci.
+      sY/////////y  caa           S//P   |             -- Sebastien Chabal
+       cayCyayP//Ya              pY/Ya   |
+        sY/PsY////YCc          aC//Yp
+         sc  sccaCY//PCypaapyCP//YSs
+                  spCPY//////YPSps
+                       ccaacs
+                                       using IPython 7.13.0
+   >>>
+   >>>
+   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2")]
+   >>>
+   >>>wrpcap("in_net_pcap1.pcap",pkts)
+   >>>
+   >>>
+   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="20.0.2.2")]
+   >>>
+   >>>wrpcap("in_net_pcap0.pcap",pkts)
+   >>>
+   >>> quit
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [EXT] [PATCH v10 12/12] app/graph: support l3fwd use case
  2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
@ 2023-10-19 12:28                                 ` Jerin Jacob Kollanukkaran
  2023-10-23  7:06                                   ` Nithin Dabilpuram
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
  1 sibling, 1 reply; 182+ messages in thread
From: Jerin Jacob Kollanukkaran @ 2023-10-19 12:28 UTC (permalink / raw)
  To: Sunil Kumar Kori, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

> -----Original Message-----
> From: skori@marvell.com <skori@marvell.com>
> Sent: Thursday, October 19, 2023 4:20 PM
> To: Sunil Kumar Kori <skori@marvell.com>; Rakesh Kudurumalla
> <rkudurumalla@marvell.com>
> Cc: dev@dpdk.org
> Subject: [EXT] [PATCH v10 12/12] app/graph: support l3fwd use case
> 
> External Email
> 
> ----------------------------------------------------------------------
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> 
> Adds an use case l3fwd. It contains a dedicated l3fwd.cli file mentioning
> commands to configure the required resources.
> 
> Once application successfully parses the l3fwd.cli then a graph is created having
> below nodes:
>  - ethdev_rx -> pkt_cls
> 
>  - pkt_cls -> ip4_lookup
>  - pkt_cls -> ip6_lookup
>  - pkt_cls -> pkt_drop
> 
>  - ip4_lookup -> ip4_rewrite
>  - ip4_lookup -> pkt_drop
> 
>  - ip6_lookup -> ip6_rewrite
>  - ip6_lookup -> pkt_drop
> 
>  - ip4_rewrite -> ethdev_tx
>  - ip4_rewrite -> pkt_drop
> 
>  - ip6_rewrite -> ethdev_tx
>  - ip6_rewrite -> pkt_drop
> 
>  - ethdev_tx -> pkt_drop
> 
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
> +Supported Use cases
> +-------------------
> + * l3fwd
> +
> +This use case is supported for both PF and PCAP network devices. To

Both HW and PCAP vdev network device.
Remove PF instance form doc.

> +demonstrate, corresponding .cli files are available at
> +``<dpdk_root_dir/app/graph/examples/>``
> +named as ``l3fwd.cli`` and  ``l3fwd_pcap.cli`` respectively.
> +
>  Running the Application
>  -----------------------
> 
> @@ -63,6 +71,26 @@ Following are the application command-line options:
> 
>         Dumps application usage
> 
> +Examples
> +~~~~~~~~

In order to have continuity,  Please move "16.1. Supported Use cases" here and remove "Examples"

16.x Supported Use case
16.x.1 L3fwd 
16.x.1.1 Example commands 
16.x.1.2 Verifying traffic 


> +
> +For PF devices
> +
> +.. code-block:: console
> +
> +   ./dpdk-graph -c 0xff -a 0002:02:00.0 -a 0002:03:00.0 --
> +             -s <dpdk_root_dir>/app/graph/examples/l3fwd.cli
> +
> +For net_pcapX devices
> +
> +.. code-block:: console
> +
> +   ./dpdk-graph -c 0xff --
> vdev=net_pcap0,rx_pcap=in_net_pcap0.pcap,tx_pcap=out_net_pcap1.pcap
> +                        --
> vdev=net_pcap1,rx_pcap=in_net_pcap1.pcap,tx_pcap=out_net_pcap0.pcap
> +                        -- -s
> + <dpdk_root_dir>/app/graph/examples/l3fwd_pcap.cli
> +
> +Refer section :ref:`verifying_traffic` to create .pcap file used here.
> +
>  Supported CLI commands
>  ----------------------
> 
> @@ -223,3 +251,84 @@ Created graph for use case
> 
>  On the successful execution of ``<usecase>.cli`` file, corresponding graph will
> be created.
>  This section mentions the created graph for each use case.
> +
> +l3fwd
> +~~~~~
> +
> +.. _figure_l3fwd_graph:
> +
> +.. figure:: img/graph-usecase-l3fwd.*
> +
> +.. _verifying_traffic:
> +
> +Verifying traffic
> +~~~~~~~~~~~~~~~~~
> +
> +``l3fwd.cli`` and ``l3fwd_pcap.cli`` creates setup with two network
> +ports. Routing between these ports are done by lookup node routing
> +information. For current use case, following routing table is used:
> +
> +.. code-block:: console
> +
> +   DIP        port
> +   10.0.2.2    1
> +   20.0.2.2    0
> +
> +On the successful execution of ``l3fwd.cli`` or ``l3fwd_pcap.cli``,
> +user needs to send traffic with mentioned DIP.
> +
> +For net_pcapX devices, required pcap file should be created and passed
> +to application. These pcap files can be created in several ways. Scapy is one of
> the method to get the same:
> +
> +.. code-block:: console
> +
> +   # scapy
> +
> +                     aSPY//YASa
> +             apyyyyCY//////////YCa       |
> +            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
> +    ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.3
> +    AYAsAYYYYYYYY///Ps              cY//S   |
> +         pCCCCY//p          cSSps y//Y   |
> https://urldefense.proofpoint.com/v2/url?u=https-
> 3A__github.com_secdev_scapy&d=DwIDAg&c=nKjWec2b6R0mOyPaz7xtfQ&r=
> 1DGob4H4rxz6H8uITozGOCa0s5f4wCNtTa4UUKvcsvI&m=V_QcQyFL-
> NxiuHEAdNEZlQ379HvK37suZH_8Yfuuz-HwEKmVw5Iy-SbtS95-
> brBb&s=XR2R_CEDkRJPhayLXSY1ZRnzrZsuR-UDaSSDHELwAnQ&e=
> +         SPPPP///a          pP///AC//Y   |
> +              A//A            cyP////C   | Have fun!
> +              p///Ac            sC///a   |
> +              P////YCpc           A//A   | We are in France, we say Skappee.
> +       scccccp///pSP///p          p//Y   | OK? Merci.
> +      sY/////////y  caa           S//P   |             -- Sebastien Chabal
> +       cayCyayP//Ya              pY/Ya   |
> +        sY/PsY////YCc          aC//Yp
> +         sc  sccaCY//PCypaapyCP//YSs
> +                  spCPY//////YPSps
> +                       ccaacs
> +                                       using IPython 7.13.0

This graphics can be removed in the doc

> +   >>>
> +   >>>

Remove this

> +   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2")]
> +   >>>
> +   >>>wrpcap("in_net_pcap1.pcap",pkts)
> +   >>>
> +   >>>

Remove above two lines

> +   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> +             Ether(dst="FA:09:F9:D7:E0:9D",
> src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="20.0.2.2")]
> +   >>>
> +   >>>wrpcap("in_net_pcap0.pcap",pkts)
> +   >>>

Remove above line

> +   >>> quit

Which above changes:
Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 00/12] add CLI based graph application
  2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
  2023-10-19 12:28                                 ` [EXT] " Jerin Jacob Kollanukkaran
@ 2023-10-19 17:29                                 ` skori
  2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
                                                     ` (13 more replies)
  1 sibling, 14 replies; 182+ messages in thread
From: skori @ 2023-10-19 17:29 UTC (permalink / raw)
  Cc: dev, Sunil Kumar Kori

From: Sunil Kumar Kori <skori@marvell.com>

In the continuation of following feedback
https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
this patch series adds dpdk-graph application to exercise various
usecases using graph.

1. Each use case is defined in terms of .cli file which will contain
set of commands to configure the system and to create a graph for
that use case.

2. Each module like ethdev, mempool, route etc exposes its set of commands
to do global and node specific configuration.

3. Command parsing is backed by command line library.

Rakesh Kudurumalla (5):
  app/graph: support mempool command line interfaces
  app/graph: support IPv6 lookup command line interfaces
  app/graph: support ethdev Rx command line interfaces
  app/graph: support graph command line interfaces
  app/graph: support l3fwd use case

Sunil Kumar Kori (7):
  app/graph: support application CLI framework
  app/graph: support telnet connectivity framework
  app/graph: support parser utility APIs
  app/graph: support ethdev command line interfaces
  app/graph: support IPv4 lookup command line interfaces
  app/graph: support neigh command line interfaces
  app/graph: support CLI option to enable graph stats

 MAINTAINERS                                  |   7 +
 app/graph/cli.c                              | 138 +++
 app/graph/cli.h                              |  32 +
 app/graph/conn.c                             | 284 ++++++
 app/graph/conn.h                             |  46 +
 app/graph/ethdev.c                           | 890 +++++++++++++++++++
 app/graph/ethdev.h                           |  40 +
 app/graph/ethdev_priv.h                      | 112 +++
 app/graph/ethdev_rx.c                        | 165 ++++
 app/graph/ethdev_rx.h                        |  37 +
 app/graph/ethdev_rx_priv.h                   |  39 +
 app/graph/examples/l3fwd.cli                 |  73 ++
 app/graph/examples/l3fwd_pcap.cli            |  71 ++
 app/graph/graph.c                            | 550 ++++++++++++
 app/graph/graph.h                            |  21 +
 app/graph/graph_priv.h                       |  70 ++
 app/graph/ip4_route.c                        | 224 +++++
 app/graph/ip6_route.c                        | 229 +++++
 app/graph/l3fwd.c                            | 136 +++
 app/graph/l3fwd.h                            |  11 +
 app/graph/main.c                             | 237 +++++
 app/graph/mempool.c                          | 140 +++
 app/graph/mempool.h                          |  24 +
 app/graph/mempool_priv.h                     |  34 +
 app/graph/meson.build                        |  25 +
 app/graph/module_api.h                       |  31 +
 app/graph/neigh.c                            | 364 ++++++++
 app/graph/neigh.h                            |  17 +
 app/graph/neigh_priv.h                       |  49 +
 app/graph/route.h                            |  40 +
 app/graph/route_priv.h                       |  44 +
 app/graph/utils.c                            | 156 ++++
 app/graph/utils.h                            |  14 +
 app/meson.build                              |   1 +
 doc/guides/rel_notes/release_23_11.rst       |   7 +
 doc/guides/tools/graph.rst                   | 314 +++++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
 doc/guides/tools/index.rst                   |   1 +
 38 files changed, 4883 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/examples/l3fwd_pcap.cli
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/ip6_route.c
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h
 create mode 100644 doc/guides/tools/graph.rst
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 01/12] app/graph: support application CLI framework
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:03                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 02/12] app/graph: support telnet connectivity framework skori
                                                     ` (12 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Thomas Monjalon, Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds base framework to read a given .cli file as a command line
parameter "-s".

Example:
 # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli

Each .cli file will contain commands to configure different module like
mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
commandline library.

Each module needs to expose its supported commands & corresponding
callback functions to commandline library to get them parsed.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
v10..v11
 - Add information to kill telnet session in user guide.
 - Merge l3fwd related information in a single section in user guide.
 - Fix spellings.

v9..v10
 - Add l3fwd_pcap.cli for pcap devices.
 - Update table generation mechanism user guide document

v8..v9:
 - Replace strcpy() to rte_strscpy()
 - Update release note.
 - Update user guide

v7..v8:
 - Fix klocwork issues.

v6..v7:
 - Fix FreeBSD build error.
 - Make route and neigh runtime configuration too.

v5..v6:
 - Fix build errors.
 - Fix checkpatch errors.
 - Fix individual patch build errors.

v4..v5:
 - Fix application exit issue.
 - Enable graph packet capture feature.
 - Fix graph coremask synchronization with eal coremask.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/

v3..v4:
 - Use commandline library to parse command tokens.
 - Split to multiple smaller patches.
 - Make neigh and route as dynamic database.
 - add ethdev and graph stats command via telnet.
 - Update user guide.

https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/

 MAINTAINERS                            |   7 ++
 app/graph/cli.c                        | 115 ++++++++++++++++++++++
 app/graph/cli.h                        |  32 +++++++
 app/graph/main.c                       | 128 +++++++++++++++++++++++++
 app/graph/meson.build                  |  15 +++
 app/graph/module_api.h                 |  16 ++++
 app/meson.build                        |   1 +
 doc/guides/rel_notes/release_23_11.rst |   7 ++
 doc/guides/tools/graph.rst             |  75 +++++++++++++++
 doc/guides/tools/index.rst             |   1 +
 10 files changed, 397 insertions(+)
 create mode 100644 app/graph/cli.c
 create mode 100644 app/graph/cli.h
 create mode 100644 app/graph/main.c
 create mode 100644 app/graph/meson.build
 create mode 100644 app/graph/module_api.h
 create mode 100644 doc/guides/tools/graph.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 4083658697..88a71d5455 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1818,6 +1818,13 @@ F: dts/
 F: devtools/dts-check-format.sh
 F: doc/guides/tools/dts.rst
 
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
 
 Other Example Applications
 --------------------------
diff --git a/app/graph/cli.c b/app/graph/cli.c
new file mode 100644
index 0000000000..df4f8fcbb8
--- /dev/null
+++ b/app/graph/cli.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define CMD_MAX_TOKENS 256
+#define MAX_LINE_SIZE 2048
+
+cmdline_parse_ctx_t modules_ctx[] = {
+	NULL,
+};
+
+static struct cmdline *cl;
+
+static int
+is_comment(char *in)
+{
+	if ((strlen(in) && index("!#%;", in[0])) ||
+		(strncmp(in, "//", 2) == 0) ||
+		(strncmp(in, "--", 2) == 0))
+		return 1;
+
+	return 0;
+}
+
+void
+cli_init(void)
+{
+	cl = cmdline_stdin_new(modules_ctx, "");
+}
+
+void
+cli_exit(void)
+{
+	cmdline_stdin_exit(cl);
+}
+
+void
+cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
+{
+	int rc;
+
+	if (is_comment(in))
+		return;
+
+	rc = cmdline_parse(cl, in);
+	if (rc == CMDLINE_PARSE_AMBIGUOUS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
+	else if (rc == CMDLINE_PARSE_NOMATCH)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
+	else if (rc == CMDLINE_PARSE_BAD_ARGS)
+		snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
+
+	return;
+
+}
+
+int
+cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
+{
+	char *msg_in = NULL, *msg_out = NULL;
+	int rc = -EINVAL;
+	FILE *f = NULL;
+
+	/* Check input arguments */
+	if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
+	    (msg_out_len_max == 0))
+		return rc;
+
+	msg_in = malloc(msg_in_len_max + 1);
+	msg_out = malloc(msg_out_len_max + 1);
+	if ((msg_in == NULL) || (msg_out == NULL)) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	/* Open input file */
+	f = fopen(file_name, "r");
+	if (f == NULL) {
+		rc = -EIO;
+		goto exit;
+	}
+
+	/* Read file */
+	while (fgets(msg_in, msg_in_len_max, f) != NULL) {
+		msg_out[0] = 0;
+
+		cli_process(msg_in, msg_out, msg_out_len_max, obj);
+
+		if (strlen(msg_out))
+			printf("%s", msg_out);
+	}
+
+	/* Close file */
+	fclose(f);
+	rc = 0;
+
+exit:
+	free(msg_out);
+	free(msg_in);
+	return rc;
+}
diff --git a/app/graph/cli.h b/app/graph/cli.h
new file mode 100644
index 0000000000..652f948352
--- /dev/null
+++ b/app/graph/cli.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CLI_H
+#define APP_GRAPH_CLI_H
+
+/* Macros */
+#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
+#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
+#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
+#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
+#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
+#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
+#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
+#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
+#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
+#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
+#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
+
+#define APP_CLI_CMD_NAME_SIZE	64
+
+void cli_init(void);
+
+void cli_exit(void);
+
+void cli_process(char *in, char *out, size_t out_size, void *arg);
+
+int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
+		       void *arg);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
new file mode 100644
index 0000000000..734a94444e
--- /dev/null
+++ b/app/graph/main.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <rte_eal.h>
+#include <rte_launch.h>
+
+#include "module_api.h"
+
+volatile bool force_quit;
+
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+			    "[--help]\n";
+
+static struct app_params {
+	char *script_name;
+} app = {
+	.script_name = NULL,
+};
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		printf("\n\nSignal %d received, preparing to exit...\n", signum);
+		force_quit = true;
+	}
+}
+
+static int
+app_args_parse(int argc, char **argv)
+{
+	struct option lgopts[] = {
+		{"help", 0, 0, 'H'},
+	};
+	int s_present, n_args, i;
+	char *app_name = argv[0];
+	int opt, option_index;
+
+	/* Skip EAL input args */
+	n_args = argc;
+	for (i = 0; i < n_args; i++)
+		if (strcmp(argv[i], "--") == 0) {
+			argc -= i;
+			argv += i;
+			break;
+		}
+
+	if (i == n_args)
+		return 0;
+
+	/* Parse args */
+	s_present = 0;
+
+	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+		switch (opt) {
+		case 's':
+			if (s_present) {
+				printf("Error: Multiple -s arguments\n");
+				return -1;
+			}
+			s_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -s not provided\n");
+				return -1;
+			}
+
+			app.script_name = strdup(optarg);
+			if (app.script_name == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'H':
+		default:
+			printf(usage, app_name);
+			return -1;
+		}
+	}
+	optind = 1; /* reset getopt lib */
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	int rc;
+
+	/* Parse application arguments */
+	rc = app_args_parse(argc, argv);
+	if (rc < 0)
+		return rc;
+
+	/* EAL */
+	rc = rte_eal_init(argc, argv);
+	if (rc < 0) {
+		printf("Error: EAL initialization failed (%d)\n", rc);
+		return rc;
+	};
+
+	force_quit = false;
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	cli_init();
+
+	/* Script */
+	if (app.script_name) {
+		cli_script_process(app.script_name, 0,
+			0, NULL);
+	}
+
+	cli_exit();
+	rte_eal_cleanup();
+	return 0;
+}
diff --git a/app/graph/meson.build b/app/graph/meson.build
new file mode 100644
index 0000000000..ed33a04476
--- /dev/null
+++ b/app/graph/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Marvell.
+
+# override default name to drop the hyphen
+name = 'graph'
+build = cc.has_header('sys/epoll.h')
+if not build
+    subdir_done()
+endif
+
+deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
+sources = files(
+        'cli.c',
+        'main.c',
+)
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
new file mode 100644
index 0000000000..372aeae7e3
--- /dev/null
+++ b/app/graph/module_api.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MODULE_API_H
+#define APP_GRAPH_MODULE_API_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "cli.h"
+/*
+ * Externs
+ */
+extern volatile bool force_quit;
+
+#endif
diff --git a/app/meson.build b/app/meson.build
index e4bf5c531c..728c936383 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -17,6 +17,7 @@ endif
 apps = [
         'dumpcap',
         'pdump',
+        'graph',
         'proc-info',
         'test-acl',
         'test-bbdev',
diff --git a/doc/guides/rel_notes/release_23_11.rst b/doc/guides/rel_notes/release_23_11.rst
index 0a6fc76a9d..f43b416565 100644
--- a/doc/guides/rel_notes/release_23_11.rst
+++ b/doc/guides/rel_notes/release_23_11.rst
@@ -243,6 +243,13 @@ New Features
   Added dispatcher library which purpose is to help decouple different
   parts (modules) of an eventdev-based application.
 
+* **Added CLI based graph application.**
+
+  Added CLI based graph application which exercises on different usecases.
+  Application provides a framework so that each usecase can be added via CLI
+  file. Each CLI will further be translated into a graph representing user's
+  required usecase.
+
 
 Removed Items
 -------------
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
new file mode 100644
index 0000000000..cb005e7856
--- /dev/null
+++ b/doc/guides/tools/graph.rst
@@ -0,0 +1,75 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2023 Marvell.
+
+dpdk-graph Application
+======================
+
+The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
+application that allows exercising various graph use cases.
+This application has a generic framework to add new graph based use cases to
+verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
+Based on the input file, application creates a graph to cater the use case.
+
+Also this application framework can be used by other graph based applications.
+
+Running the Application
+-----------------------
+
+The application has a number of command line options which can be provided in
+following syntax
+
+.. code-block:: console
+
+   dpdk-graph [EAL Options] -- [application options]
+
+EAL Options
+~~~~~~~~~~~
+
+Following are the EAL command-line options that can be used in conjunction
+with the ``dpdk-graph`` application.
+See the DPDK Getting Started Guides for more information on these options.
+
+*   ``-c <COREMASK>`` or ``-l <CORELIST>``
+
+        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
+        list of cores to be used.
+
+Application Options
+~~~~~~~~~~~~~~~~~~~
+
+Following are the application command-line options:
+
+* ``-s``
+
+        Script name with absolute path which specifies the use case. It is
+        a mandatory parameter which will be used to create desired graph
+        for a given use case.
+
+* ``--help``
+
+       Dumps application usage
+
+Supported CLI commands
+----------------------
+
+This section provides details on commands which can be used in ``<usecase>.cli``
+file to express the requested use case configuration.
+
+.. table:: Exposed CLIs
+   :widths: auto
+
+   +--------------------------------------+-----------------------------------+---------+----------+
+   |               Command                |             Description           | Dynamic | Optional |
+   +======================================+===================================+=========+==========+
+   |                                      |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+
+Runtime configuration
+---------------------
+
+
+Created graph for use case
+--------------------------
+
+On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
+This section mentions the created graph for each use case.
diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
index f2afb1fcc5..4f4dc8b518 100644
--- a/doc/guides/tools/index.rst
+++ b/doc/guides/tools/index.rst
@@ -23,4 +23,5 @@ DPDK Tools User Guides
     testeventdev
     testregex
     testmldev
+    graph
     dts
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 02/12] app/graph: support telnet connectivity framework
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
  2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:03                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 03/12] app/graph: support parser utility APIs skori
                                                     ` (11 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds framework to initiate a telnet session with application.

Some configurations and debug commands are exposed as runtime APIs.
Those commands can be invoked using telnet session.

Application initiates a telnet server with host address 0.0.0.0
and port number 8086 by default.

To make it configurable, "-h" and "-p" options are provided.
Using them user can pass host address and port number on which
application will start telnet server.

Using same host address and port number, telnet client can connect
to application.

Syntax to connect with application:
	# telnet <host> <port>

Once session is connected, "graph> " prompt will be available.
Example:
	# telnet 10.28.35.207 50000
	  Trying 10.28.35.207...
	  Connected to 10.28.35.207.
	  Escape character is '^]'.

	  Welcome!

	  graph>

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
 app/graph/conn.h           |  46 ++++++
 app/graph/main.c           | 103 +++++++++++++-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   3 +
 doc/guides/tools/graph.rst |  38 +++++
 6 files changed, 470 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/conn.c
 create mode 100644 app/graph/conn.h

diff --git a/app/graph/conn.c b/app/graph/conn.c
new file mode 100644
index 0000000000..44934602c7
--- /dev/null
+++ b/app/graph/conn.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <rte_string_fns.h>
+
+#include "module_api.h"
+
+#define MSG_CMD_TOO_LONG "Command too long."
+
+static int
+data_event_handle(struct conn *conn, int fd_client)
+{
+	ssize_t len, i, rc = 0;
+
+	/* Read input message */
+	len = read(fd_client, conn->buf, conn->buf_size);
+	if (len == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	if (len == 0)
+		return rc;
+
+	/* Handle input messages */
+	for (i = 0; i < len; i++) {
+		if (conn->buf[i] == '\n') {
+			size_t n;
+
+			conn->msg_in[conn->msg_in_len] = 0;
+			conn->msg_out[0] = 0;
+
+			conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
+					 conn->msg_handle_arg);
+
+			n = strlen(conn->msg_out);
+			if (n) {
+				rc = write(fd_client, conn->msg_out, n);
+				if (rc == -1)
+					goto exit;
+			}
+
+			conn->msg_in_len = 0;
+		} else if (conn->msg_in_len < conn->msg_in_len_max) {
+			conn->msg_in[conn->msg_in_len] = conn->buf[i];
+			conn->msg_in_len++;
+		} else {
+			rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
+			if (rc == -1)
+				goto exit;
+
+			conn->msg_in_len = 0;
+		}
+	}
+
+	/* Write prompt */
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	rc = (rc == -1) ? -1 : 0;
+
+exit:
+	return rc;
+}
+
+static int
+control_event_handle(struct conn *conn, int fd_client)
+{
+	int rc;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
+	if (rc == -1)
+		goto exit;
+
+	rc = close(fd_client);
+	if (rc == -1)
+		goto exit;
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+struct conn *
+conn_init(struct conn_params *p)
+{
+	int fd_server, fd_client_group, rc;
+	struct sockaddr_in server_address;
+	struct conn *conn = NULL;
+	int reuse = 1;
+
+	memset(&server_address, 0, sizeof(server_address));
+
+	/* Check input arguments */
+	if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
+	    (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
+	    (p->msg_handle == NULL))
+		goto exit;
+
+	rc = inet_aton(p->addr, &server_address.sin_addr);
+	if (rc == 0)
+		goto exit;
+
+	/* Memory allocation */
+	conn = calloc(1, sizeof(struct conn));
+	if (conn == NULL)
+		goto exit;
+
+	conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
+	conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
+	conn->buf = calloc(1, p->buf_size);
+	conn->msg_in = calloc(1, p->msg_in_len_max + 1);
+	conn->msg_out = calloc(1, p->msg_out_len_max + 1);
+
+	if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
+	    (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	/* Server socket */
+	server_address.sin_family = AF_INET;
+	server_address.sin_port = htons(p->port);
+
+	fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (fd_server == -1) {
+		conn_free(conn);
+		conn = NULL;
+		goto exit;
+	}
+
+	if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
+		       sizeof(reuse)) < 0)
+		goto free;
+
+	rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
+	if (rc == -1)
+		goto free;
+
+	rc = listen(fd_server, 16);
+	if (rc == -1)
+		goto free;
+
+	/* Client group */
+	fd_client_group = epoll_create(1);
+	if (fd_client_group == -1)
+		goto free;
+
+	/* Fill in */
+	rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
+	rte_strscpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
+	conn->buf_size = p->buf_size;
+	conn->msg_in_len_max = p->msg_in_len_max;
+	conn->msg_out_len_max = p->msg_out_len_max;
+	conn->msg_in_len = 0;
+	conn->fd_server = fd_server;
+	conn->fd_client_group = fd_client_group;
+	conn->msg_handle = p->msg_handle;
+	conn->msg_handle_arg = p->msg_handle_arg;
+
+exit:
+	return conn;
+free:
+	conn_free(conn);
+	close(fd_server);
+	conn = NULL;
+	return conn;
+}
+
+void
+conn_free(struct conn *conn)
+{
+	if (conn == NULL)
+		return;
+
+	if (conn->fd_client_group)
+		close(conn->fd_client_group);
+
+	if (conn->fd_server)
+		close(conn->fd_server);
+
+	free(conn->msg_out);
+	free(conn->msg_in);
+	free(conn->prompt);
+	free(conn->welcome);
+	free(conn);
+}
+
+int
+conn_req_poll(struct conn *conn)
+{
+	struct sockaddr_in client_address;
+	socklen_t client_address_length;
+	struct epoll_event event;
+	int fd_client, rc;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Server socket */
+	client_address_length = sizeof(client_address);
+	fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
+			    &client_address_length, SOCK_NONBLOCK);
+	if (fd_client == -1) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return 0;
+
+		return -1;
+	}
+
+	/* Client group */
+	event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
+	event.data.fd = fd_client;
+
+	rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	/* Client */
+	rc = write(fd_client, conn->welcome, strlen(conn->welcome));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = write(fd_client, conn->prompt, strlen(conn->prompt));
+	if (rc == -1) {
+		close(fd_client);
+		goto exit;
+	}
+
+	rc = 0;
+
+exit:
+	return rc;
+}
+
+int
+conn_msg_poll(struct conn *conn)
+{
+	int fd_client, rc, rc_data = 0, rc_control = 0;
+	struct epoll_event event;
+
+	/* Check input arguments */
+	if (conn == NULL)
+		return -1;
+
+	/* Client group */
+	rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
+	if ((rc == -1) || rc == 0)
+		return rc;
+
+	fd_client = event.data.fd;
+
+	/* Data available */
+	if (event.events & EPOLLIN)
+		rc_data = data_event_handle(conn, fd_client);
+
+	/* Control events */
+	if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
+		rc_control = control_event_handle(conn, fd_client);
+
+	if (rc_data || rc_control)
+		return -1;
+
+	return 0;
+}
diff --git a/app/graph/conn.h b/app/graph/conn.h
new file mode 100644
index 0000000000..770964cf4c
--- /dev/null
+++ b/app/graph/conn.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_CONN_H
+#define APP_GRAPH_CONN_H
+
+#define CONN_WELCOME_LEN_MAX 1024
+#define CONN_PROMPT_LEN_MAX 16
+
+typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
+
+struct conn {
+	char *welcome;
+	char *prompt;
+	char *buf;
+	char *msg_in;
+	char *msg_out;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	size_t msg_in_len;
+	int fd_server;
+	int fd_client_group;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn_params {
+	const char *welcome;
+	const char *prompt;
+	const char *addr;
+	uint16_t port;
+	size_t buf_size;
+	size_t msg_in_len_max;
+	size_t msg_out_len_max;
+	conn_msg_handle_t msg_handle;
+	void *msg_handle_arg;
+};
+
+struct conn *conn_init(struct conn_params *p);
+void conn_free(struct conn *conn);
+int conn_req_poll(struct conn *conn);
+int conn_msg_poll(struct conn *conn);
+
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 734a94444e..96548f49e7 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -2,6 +2,7 @@
  * Copyright(c) 2023 Marvell.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
@@ -11,19 +12,33 @@
 #include <sys/select.h>
 #include <unistd.h>
 
+#include <rte_cycles.h>
 #include <rte_eal.h>
 #include <rte_launch.h>
 
 #include "module_api.h"
 
 volatile bool force_quit;
+struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
 			    "[--help]\n";
 
 static struct app_params {
+	struct conn_params conn;
 	char *script_name;
 } app = {
+	.conn = {
+		.welcome = "\nWelcome!\n\n",
+		.prompt = "graph> ",
+		.addr = "0.0.0.0",
+		.port = 8086,
+		.buf_size = 1024 * 1024,
+		.msg_in_len_max = 1024,
+		.msg_out_len_max = 1024 * 1024,
+		.msg_handle = cli_process,
+		.msg_handle_arg = NULL, /* set later. */
+	},
 	.script_name = NULL,
 };
 
@@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
 	};
-	int s_present, n_args, i;
+	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
 	int opt, option_index;
 
@@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
 		return 0;
 
 	/* Parse args */
+	h_present = 0;
+	p_present = 0;
 	s_present = 0;
 
-	while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
+	while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
 		switch (opt) {
+		case 'h':
+			if (h_present) {
+				printf("Error: Multiple -h arguments\n");
+				return -1;
+			}
+			h_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -h not provided\n");
+				return -1;
+			}
+
+			app.conn.addr = strdup(optarg);
+			if (app.conn.addr == NULL) {
+				printf("Error: Not enough memory\n");
+				return -1;
+			}
+			break;
+
+		case 'p':
+			if (p_present) {
+				printf("Error: Multiple -p arguments\n");
+				return -1;
+			}
+			p_present = 1;
+
+			if (!strlen(optarg)) {
+				printf("Error: Argument for -p not provided\n");
+				return -1;
+			}
+
+			app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
+			break;
+
 		case 's':
 			if (s_present) {
 				printf("Error: Multiple -s arguments\n");
@@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_exit(void)
+{
+	struct timeval tv;
+	fd_set fds;
+	int ret;
+	char c;
+
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 100;
+	ret = select(1, &fds, NULL, NULL, &tv);
+	if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
+		return true;
+	else
+		return false;
+
+}
+
 int
 main(int argc, char **argv)
 {
@@ -118,10 +189,32 @@ main(int argc, char **argv)
 
 	/* Script */
 	if (app.script_name) {
-		cli_script_process(app.script_name, 0,
-			0, NULL);
+		cli_script_process(app.script_name, app.conn.msg_in_len_max,
+			app.conn.msg_out_len_max, NULL);
+	}
+
+	/* Connectivity */
+	app.conn.msg_handle_arg = NULL;
+	conn = conn_init(&app.conn);
+	if (!conn) {
+		printf("Error: Connectivity initialization failed\n");
+		goto exit;
+	};
+
+	rte_delay_ms(1);
+	printf("Press enter to exit\n");
+
+	/* Dispatch loop */
+	while (!force_quit) {
+		conn_req_poll(conn);
+
+		conn_msg_poll(conn);
+		if (app_graph_exit())
+			force_quit = true;
 	}
 
+exit:
+	conn_free(conn);
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index ed33a04476..c8d2b41b69 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -11,5 +11,6 @@ endif
 deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
+        'conn.c',
         'main.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 372aeae7e3..9826303f0c 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -7,10 +7,13 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+
 #include "cli.h"
+#include "conn.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
 
+bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index cb005e7856..943f915049 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -39,6 +39,16 @@ Application Options
 
 Following are the application command-line options:
 
+* ``-h``
+
+        Set the host IPv4 address over which telnet session can be opened.
+        It is an optional parameter. Default host address is 0.0.0.0.
+
+* ``-p``
+
+        Set the L4 port number over which telnet session can be opened.
+	It is an optional parameter. Default port is 8086.
+
 * ``-s``
 
         Script name with absolute path which specifies the use case. It is
@@ -67,7 +77,35 @@ file to express the requested use case configuration.
 Runtime configuration
 ---------------------
 
+Application allows some configuration to be modified at runtime using a telnet session.
+Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
+by default.
+
+if user passes ``-h`` and ``-p`` options while running application then corresponding
+IP address and port number will be used for telnet session.
+
+After successful launch of application, client can connect to application using given
+host & port and console will be accessed with prompt ``graph>``.
+
+Command to access a telnet session
+
+.. code-block:: console
+
+   telnet <host> <port>
+
+Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
+
+.. code-block:: console
+
+   $ telnet 10.28.35.207 50000
+   Trying 10.28.35.207...
+   Connected to 10.28.35.207.
+   Escape character is '^]'.
+
+   Welcome!
 
+   graph>
+   graph>
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 03/12] app/graph: support parser utility APIs
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
  2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
  2023-10-19 17:30                                   ` [PATCH v11 02/12] app/graph: support telnet connectivity framework skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:03                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 04/12] app/graph: support mempool command line interfaces skori
                                                     ` (10 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds some helper functions to parse IPv4, IPv6 and MAC addresses
string into respective datatype.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/meson.build  |   1 +
 app/graph/module_api.h |   1 +
 app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
 app/graph/utils.h      |  14 ++++
 4 files changed, 172 insertions(+)
 create mode 100644 app/graph/utils.c
 create mode 100644 app/graph/utils.h

diff --git a/app/graph/meson.build b/app/graph/meson.build
index c8d2b41b69..fd71036a95 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,4 +13,5 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 9826303f0c..ad4fb50989 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "utils.h"
 /*
  * Externs
  */
diff --git a/app/graph/utils.c b/app/graph/utils.c
new file mode 100644
index 0000000000..c7b6ae83cf
--- /dev/null
+++ b/app/graph/utils.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "module_api.h"
+
+#define white_spaces_skip(pos)			\
+({						\
+	__typeof__(pos) _p = (pos);		\
+	for ( ; isspace(*_p); _p++)		\
+		;				\
+	_p;					\
+})
+
+static void
+hex_string_to_uint64(uint64_t *dst, const char *hexs)
+{
+	char buf[2] = {0};
+	uint8_t shift = 4;
+	int iter = 0;
+	char c;
+
+	while ((c = *hexs++)) {
+		buf[0] = c;
+		*dst |= (strtol(buf, NULL, 16) << shift);
+		shift -= 4;
+		iter++;
+		if (iter == 2) {
+			iter = 0;
+			shift = 4;
+			dst++;
+		}
+	}
+}
+
+int
+parser_uint64_read(uint64_t *value, const char *p)
+{
+	char *next;
+	uint64_t val;
+
+	p = white_spaces_skip(p);
+	if (!isdigit(*p))
+		return -EINVAL;
+
+	val = strtoul(p, &next, 0);
+	if (p == next)
+		return -EINVAL;
+
+	p = next;
+	switch (*p) {
+	case 'T':
+		val *= 1024ULL;
+		/* fall through */
+	case 'G':
+		val *= 1024ULL;
+		/* fall through */
+	case 'M':
+		val *= 1024ULL;
+		/* fall through */
+	case 'k':
+	case 'K':
+		val *= 1024ULL;
+		p++;
+		break;
+	}
+
+	p = white_spaces_skip(p);
+	if (*p != '\0')
+		return -EINVAL;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_uint32_read(uint32_t *value, const char *p)
+{
+	uint64_t val = 0;
+	int rc = parser_uint64_read(&val, p);
+
+	if (rc < 0)
+		return rc;
+
+	if (val > UINT32_MAX)
+		return -ERANGE;
+
+	*value = val;
+	return 0;
+}
+
+int
+parser_ip4_read(uint32_t *value, char *p)
+{
+	uint8_t shift = 24;
+	uint32_t ip = 0;
+	char *token;
+
+	token = strtok(p, ".");
+	while (token != NULL) {
+		ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
+		token = strtok(NULL, ".");
+		shift -= 8;
+	}
+
+	*value = ip;
+
+	return 0;
+}
+
+int
+parser_ip6_read(uint8_t *value, char *p)
+{
+	uint64_t val = 0;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		*value = val;
+		token = strtok(NULL, ":");
+		value++;
+		val = 0;
+	}
+
+	return 0;
+}
+
+int
+parser_mac_read(uint64_t *value, char *p)
+{
+	uint64_t mac = 0, val = 0;
+	uint8_t shift = 40;
+	char *token;
+
+	token = strtok(p, ":");
+	while (token != NULL) {
+		hex_string_to_uint64(&val, token);
+		mac |= val << shift;
+		token = strtok(NULL, ":");
+		shift -= 8;
+		val = 0;
+	}
+
+	*value = mac;
+
+	return 0;
+}
diff --git a/app/graph/utils.h b/app/graph/utils.h
new file mode 100644
index 0000000000..0ebb5de55a
--- /dev/null
+++ b/app/graph/utils.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_UTILS_H
+#define APP_GRAPH_UTILS_H
+
+int parser_uint64_read(uint64_t *value, const char *p);
+int parser_uint32_read(uint32_t *value, const char *p);
+int parser_ip4_read(uint32_t *value, char *p);
+int parser_ip6_read(uint8_t *value, char *p);
+int parser_mac_read(uint64_t *value, char *p);
+
+#endif
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 04/12] app/graph: support mempool command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (2 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 03/12] app/graph: support parser utility APIs skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:04                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 05/12] app/graph: support ethdev " skori
                                                     ` (9 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds mempool module which will be creating mempools.

Following commands are exposed:
 - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
	cache <cache_size> numa <numa_id>
 - help mempool

User will add this command in .cli file according to its need.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
 app/graph/mempool.h        |  24 +++++++
 app/graph/mempool_priv.h   |  34 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 doc/guides/tools/graph.rst |   7 ++
 7 files changed, 210 insertions(+)
 create mode 100644 app/graph/mempool.c
 create mode 100644 app/graph/mempool.h
 create mode 100644 app/graph/mempool_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index df4f8fcbb8..cf544d5f8f 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,8 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/mempool.c b/app/graph/mempool.c
new file mode 100644
index 0000000000..9fd3f8460b
--- /dev/null
+++ b/app/graph/mempool.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_common.h>
+#include <rte_mbuf.h>
+
+#include "mempool_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
+		     "cache <cache_size> numa <numa_id>";
+
+struct mempools mpconfig;
+
+int
+mempool_process(struct mempool_config *config)
+{
+	struct rte_mempool *mp;
+	uint8_t nb_pools;
+
+	nb_pools = mpconfig.nb_pools;
+	rte_strscpy(mpconfig.config[nb_pools].name, config->name, RTE_MEMPOOL_NAMESIZE);
+	mpconfig.config[nb_pools].pool_size = config->pool_size;
+	mpconfig.config[nb_pools].buffer_size = config->buffer_size;
+	mpconfig.config[nb_pools].cache_size = config->cache_size;
+	mpconfig.config[nb_pools].numa_node = config->numa_node;
+
+	mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
+		128, config->buffer_size, config->numa_node);
+	if (!mp)
+		return -EINVAL;
+
+	mpconfig.mp[nb_pools] = mp;
+	nb_pools++;
+	mpconfig.nb_pools = nb_pools;
+
+	return 0;
+}
+
+static void
+cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		 __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- mempool command help -----------------------------",
+		 cmd_mempool_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct mempool_config_cmd_tokens *res = parsed_result;
+	struct mempool_config config;
+	int rc = -EINVAL;
+
+
+	rte_strscpy(config.name, res->name, RTE_MEMPOOL_NAMESIZE);
+	config.name[strlen(res->name)] = '\0';
+	config.pool_size = res->nb_bufs;
+	config.buffer_size = res->buf_sz;
+	config.cache_size = res->cache_size;
+	config.numa_node = res->node;
+
+	rc = mempool_process(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, "mempool");
+}
+
+cmdline_parse_token_string_t mempool_config_add_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
+cmdline_parse_token_string_t mempool_config_add_name =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
+cmdline_parse_token_string_t mempool_config_add_size =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
+cmdline_parse_token_num_t mempool_config_add_buf_sz =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_buffers =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
+cmdline_parse_token_num_t mempool_config_add_nb_bufs =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_cache =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
+cmdline_parse_token_num_t mempool_config_add_cache_size =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
+cmdline_parse_token_string_t mempool_config_add_numa =
+	TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
+cmdline_parse_token_num_t mempool_config_add_node =
+	TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
+
+cmdline_parse_inst_t mempool_config_cmd_ctx = {
+	.f = cli_mempool,
+	.data = NULL,
+	.help_str = cmd_mempool_help,
+	.tokens = {
+		(void *)&mempool_config_add_mempool,
+		(void *)&mempool_config_add_name,
+		(void *)&mempool_config_add_size,
+		(void *)&mempool_config_add_buf_sz,
+		(void *)&mempool_config_add_buffers,
+		(void *)&mempool_config_add_nb_bufs,
+		(void *)&mempool_config_add_cache,
+		(void *)&mempool_config_add_cache_size,
+		(void *)&mempool_config_add_numa,
+		(void *)&mempool_config_add_node,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t mempool_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t mempool_help_mempool =
+	TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
+
+cmdline_parse_inst_t mempool_help_cmd_ctx = {
+	.f = cli_mempool_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&mempool_help_cmd,
+		(void *)&mempool_help_mempool,
+		NULL,
+	},
+};
diff --git a/app/graph/mempool.h b/app/graph/mempool.h
new file mode 100644
index 0000000000..0808c4259e
--- /dev/null
+++ b/app/graph/mempool.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_H
+#define APP_GRAPH_MEMPOOL_H
+
+#include <cmdline_parse.h>
+#include <rte_mempool.h>
+
+struct mempool_config {
+	char name[RTE_MEMPOOL_NAMESIZE];
+	int pool_size;
+	int cache_size;
+	int buffer_size;
+	int numa_node;
+};
+
+extern cmdline_parse_inst_t mempool_config_cmd_ctx;
+extern cmdline_parse_inst_t mempool_help_cmd_ctx;
+
+int mempool_process(struct mempool_config *config);
+
+#endif
diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
new file mode 100644
index 0000000000..3ce64702a9
--- /dev/null
+++ b/app/graph/mempool_priv.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_MEMPOOL_PRIV_H
+#define APP_GRAPH_MEMPOOL_PRIV_H
+
+#include "mempool.h"
+
+struct mempool_config_cmd_tokens {
+	cmdline_fixed_string_t mempool;
+	cmdline_fixed_string_t size;
+	cmdline_fixed_string_t buffers;
+	cmdline_fixed_string_t cache;
+	cmdline_fixed_string_t numa;
+	cmdline_fixed_string_t name;
+	uint16_t buf_sz;
+	uint16_t nb_bufs;
+	uint16_t cache_size;
+	uint16_t node;
+};
+
+struct mempool_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t mempool;
+};
+
+struct mempools {
+	struct mempool_config config[RTE_MAX_ETHPORTS];
+	struct rte_mempool *mp[RTE_MAX_ETHPORTS];
+	uint8_t	nb_pools;
+};
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index fd71036a95..5dc23c875b 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,5 +13,6 @@ sources = files(
         'cli.c',
         'conn.c',
         'main.c',
+        'mempool.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index ad4fb50989..b45419811b 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,11 +10,13 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "mempool.h"
 #include "utils.h"
 /*
  * Externs
  */
 extern volatile bool force_quit;
+extern struct conn *conn;
 
 bool app_graph_exit(void);
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 943f915049..6009b0c291 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -71,6 +71,13 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    |               Command                |             Description           | Dynamic | Optional |
    +======================================+===================================+=========+==========+
+   | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
+   | | <mbuf_size> buffers                | | will be further associated to   |         |          |
+   | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
+   | | cache <cache_size> numa <numa_id>  |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
    |                                      |                                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 05/12] app/graph: support ethdev command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (3 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 04/12] app/graph: support mempool command line interfaces skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:04                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 06/12] app/graph: support IPv4 lookup " skori
                                                     ` (8 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds ethdev module to configure ethernet devices.

Following commands are exposed:
 - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
 - ethdev <ethdev_name> mtu <mtu_sz>
 - ethdev <ethdev_name> promiscuous <on/off>
 - ethdev <ethdev_name> show
 - ethdev <ethdev_name> stats
 - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
 - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
 - help ethdev

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/cli.c            |   8 +
 app/graph/ethdev.c         | 887 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev.h         |  40 ++
 app/graph/ethdev_priv.h    | 112 +++++
 app/graph/main.c           |   1 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  50 ++-
 8 files changed, 1099 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ethdev.c
 create mode 100644 app/graph/ethdev.h
 create mode 100644 app/graph/ethdev_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index cf544d5f8f..fa394fade6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -22,6 +22,14 @@
 cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
new file mode 100644
index 0000000000..8df55b4b12
--- /dev/null
+++ b/app/graph/ethdev.c
@@ -0,0 +1,887 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_bitops.h>
+#include <rte_ethdev.h>
+#include <rte_mempool.h>
+
+#include "ethdev_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
+
+static const char
+cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
+
+static const char
+cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
+
+static const char
+cmd_ethdev_stats_help[] = "ethdev <ethdev_name> stats";
+
+static const char
+cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
+
+static const char
+cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
+
+static const char
+cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
+
+static struct rte_eth_conf port_conf_default = {
+	.link_speeds = 0,
+	.rxmode = {
+		.mq_mode = RTE_ETH_MQ_RX_NONE,
+		.mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
+	},
+	.rx_adv_conf = {
+		.rss_conf = {
+			.rss_key = NULL,
+			.rss_key_len = 40,
+			.rss_hf = 0,
+		},
+	},
+	.txmode = {
+		.mq_mode = RTE_ETH_MQ_TX_NONE,
+	},
+	.lpbk_mode = 0,
+};
+
+uint32_t enabled_port_mask;
+static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
+
+
+static struct ethdev*
+ethdev_port_by_id(uint16_t port_id)
+{
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (port->config.port_id == port_id)
+			return port;
+	}
+	return NULL;
+}
+
+void *
+ethdev_mempool_list_by_portid(uint16_t portid)
+{
+	struct ethdev *port;
+
+	if (portid >= RTE_MAX_ETHPORTS)
+		return NULL;
+
+	port = ethdev_port_by_id(portid);
+	if (port)
+		return &(port->config.rx.mp);
+	else
+		return NULL;
+}
+
+int16_t
+ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		if (mask == 0) {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
+				return port->config.port_id;
+		} else {
+			if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
+				return port->config.port_id;
+		}
+	}
+
+	return portid;
+}
+
+int16_t
+ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
+{
+	int portid = -EINVAL;
+	struct ethdev *port;
+	int j;
+
+	TAILQ_FOREACH(port, &eth_node, next) {
+		for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+			if (mask == NULL) {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & port->ip6_addr.mask[j]))
+					break;
+
+			} else {
+				if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
+				    (ip[j] & mask[j]))
+					break;
+			}
+		}
+		if (j == ETHDEV_IPV6_ADDR_LEN)
+			return port->config.port_id;
+	}
+
+	return portid;
+}
+
+void
+ethdev_list_clean(void)
+{
+	struct ethdev *port;
+
+	while (!TAILQ_EMPTY(&eth_node)) {
+		port = TAILQ_FIRST(&eth_node);
+		TAILQ_REMOVE(&eth_node, port, next);
+	}
+}
+
+void
+ethdev_stop(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+		printf("Closing port %d...", portid);
+		rc = rte_eth_dev_stop(portid);
+		if (rc != 0)
+			printf("Failed to stop port %u: %s\n",
+					portid, rte_strerror(-rc));
+		rte_eth_dev_close(portid);
+		printf(" Done\n");
+	}
+
+	ethdev_list_clean();
+	rte_eal_cleanup();
+	printf("Bye...\n");
+}
+
+void
+ethdev_start(void)
+{
+	uint16_t portid;
+	int rc;
+
+	RTE_ETH_FOREACH_DEV(portid)
+	{
+		if ((enabled_port_mask & (1 << portid)) == 0)
+			continue;
+
+		rc = rte_eth_dev_start(portid);
+		if (rc < 0)
+			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
+	}
+}
+
+
+static int
+ethdev_show(const char *name)
+{
+	uint16_t mtu = 0, port_id = 0;
+	struct rte_eth_dev_info info;
+	struct rte_eth_stats stats;
+	struct rte_ether_addr addr;
+	struct rte_eth_link link;
+	uint32_t length;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rte_eth_dev_info_get(port_id, &info);
+	rte_eth_stats_get(port_id, &stats);
+	rte_eth_macaddr_get(port_id, &addr);
+	rte_eth_link_get(port_id, &link);
+	rte_eth_dev_get_mtu(port_id, &mtu);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out += length;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "%s: flags=<%s> mtu %u\n"
+		 "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
+		 "\tport# %u  speed %s\n"
+		 "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
+		 "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
+		 "\tTX errors %" PRIu64"\n\n",
+		 name,
+		 link.link_status ? "UP" : "DOWN",
+		 mtu,
+		 RTE_ETHER_ADDR_BYTES(&addr),
+		 info.nb_rx_queues,
+		 info.nb_tx_queues,
+		 port_id,
+		 rte_eth_link_speed_to_str(link.link_speed),
+		 stats.ipackets,
+		 stats.ibytes,
+		 stats.ierrors,
+		 stats.imissed,
+		 stats.rx_nombuf,
+		 stats.opackets,
+		 stats.obytes,
+		 stats.oerrors);
+
+	length = strlen(conn->msg_out);
+	conn->msg_out_len_max -= length;
+	return 0;
+}
+
+static int
+ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		eth_hdl->ip4_addr.ip = config->ip;
+		eth_hdl->ip4_addr.mask = config->mask;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc, i;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+			eth_hdl->ip6_addr.ip[i] = config->ip[i];
+			eth_hdl->ip6_addr.mask[i] = config->mask[i];
+		}
+		return 0;
+	}
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_prom_mode_config(const char *name, bool enable)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		if (enable)
+			rc = rte_eth_promiscuous_enable(portid);
+		else
+			rc = rte_eth_promiscuous_disable(portid);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.promiscuous = enable;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+static int
+ethdev_mtu_config(const char *name, uint32_t mtu)
+{
+	struct ethdev *eth_hdl;
+	uint16_t portid = 0;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &portid);
+	if (rc < 0)
+		return rc;
+
+	eth_hdl = ethdev_port_by_id(portid);
+
+	if (eth_hdl) {
+		rc = rte_eth_dev_set_mtu(portid, mtu);
+		if (rc < 0)
+			return rc;
+
+		eth_hdl->config.mtu = mtu;
+		return 0;
+	}
+
+	rc = -EINVAL;
+	return rc;
+}
+
+
+static int
+ethdev_process(const char *name, struct ethdev_config *params)
+{
+	struct rte_eth_dev_info port_info;
+	struct rte_eth_conf port_conf;
+	struct ethdev_rss_config *rss;
+	struct rte_mempool *mempool;
+	struct ethdev *ethdev_port;
+	struct rte_ether_addr smac;
+	uint16_t port_id = 0;
+	int numa_node, rc;
+	uint32_t i;
+
+	/* Check input params */
+	if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
+	    !params->tx.n_queues || !params->tx.queue_size)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	if (!ethdev_port_by_id(port_id)) {
+		ethdev_port = malloc(sizeof(struct ethdev));
+		if (!ethdev_port)
+			return -EINVAL;
+	} else {
+		return 0;
+	}
+
+	rc = rte_eth_dev_info_get(port_id, &port_info);
+	if (rc) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	mempool = rte_mempool_lookup(params->rx.mempool_name);
+	if (!mempool) {
+		rc =  -EINVAL;
+		goto exit;
+	}
+
+	params->rx.mp = mempool;
+
+	rss = params->rx.rss;
+	if (rss) {
+		if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
+			rc = -EINVAL;
+			goto exit;
+		}
+
+		for (i = 0; i < rss->n_queues; i++)
+			if (rss->queue_id[i] >= port_info.max_rx_queues) {
+				rc = -EINVAL;
+				goto exit;
+			}
+	}
+
+	/* Port */
+	memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
+	if (rss) {
+		uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
+
+		port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
+		port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
+	}
+
+	numa_node = rte_eth_dev_socket_id(port_id);
+	if (numa_node == SOCKET_ID_ANY)
+		numa_node = 0;
+
+	if (params->mtu)
+		port_conf.rxmode.mtu = params->mtu;
+
+	rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
+				       &port_conf);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	rc = rte_eth_macaddr_get(port_id, &smac);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto exit;
+	}
+
+	printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
+		smac.addr_bytes[0], smac.addr_bytes[1],
+		smac.addr_bytes[2], smac.addr_bytes[3],
+		smac.addr_bytes[4], smac.addr_bytes[5]);
+
+	/* Port RX */
+	for (i = 0; i < params->rx.n_queues; i++) {
+		rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
+			mempool);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	/* Port TX */
+	for (i = 0; i < params->tx.n_queues; i++) {
+		rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
+		if (rc < 0) {
+			rc = -EINVAL;
+			goto exit;
+		}
+	}
+
+	memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
+	memcpy(ethdev_port->config.dev_name, name, strlen(name));
+	ethdev_port->config.port_id = port_id;
+	enabled_port_mask |= RTE_BIT32(port_id);
+
+	TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
+	return 0;
+exit:
+	free(ethdev_port);
+	return rc;
+
+}
+
+static int
+ethdev_stats_show(const char *name)
+{
+	uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
+	static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
+	static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
+	uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
+	uint64_t diff_ns, diff_cycles, curr_cycles;
+	struct rte_eth_stats stats;
+	static const char *nic_stats_border = "########################";
+	uint16_t port_id, len;
+	int rc;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc < 0)
+		return rc;
+
+	rc = rte_eth_stats_get(port_id, &stats);
+	if (rc != 0) {
+		fprintf(stderr,
+			"%s: Error: failed to get stats (port %u): %d",
+			__func__, port_id, rc);
+		return rc;
+	}
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max,
+		 "\n  %s NIC statistics for port %-2d %s\n"
+		 "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
+		 "  RX-errors: %-"PRIu64"\n"
+		 "  RX-nombuf:  %-10"PRIu64"\n"
+		 "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
+		 nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
+		 stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
+		 stats.obytes);
+
+	len = strlen(conn->msg_out) - len;
+	conn->msg_out_len_max -= len;
+
+	diff_ns = 0;
+	diff_cycles = 0;
+
+	curr_cycles = rte_rdtsc();
+	if (prev_cycles[port_id] != 0)
+		diff_cycles = curr_cycles - prev_cycles[port_id];
+
+	prev_cycles[port_id] = curr_cycles;
+	diff_ns = diff_cycles > 0 ?
+		diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
+
+	diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
+		(stats.ipackets - prev_pkts_rx[port_id]) : 0;
+	diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
+		(stats.opackets - prev_pkts_tx[port_id]) : 0;
+	prev_pkts_rx[port_id] = stats.ipackets;
+	prev_pkts_tx[port_id] = stats.opackets;
+	mpps_rx = diff_ns > 0 ?
+		(double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
+	mpps_tx = diff_ns > 0 ?
+		(double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
+
+	diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
+		(stats.ibytes - prev_bytes_rx[port_id]) : 0;
+	diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
+		(stats.obytes - prev_bytes_tx[port_id]) : 0;
+	prev_bytes_rx[port_id] = stats.ibytes;
+	prev_bytes_tx[port_id] = stats.obytes;
+	mbps_rx = diff_ns > 0 ?
+		(double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
+	mbps_tx = diff_ns > 0 ?
+		(double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
+
+	len = strlen(conn->msg_out);
+	snprintf(conn->msg_out + len, conn->msg_out_len_max,
+		 "\n  Throughput (since last show)\n"
+		 "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
+		 PRIu64"          Tx-bps: %12"PRIu64"\n"
+		 "  %s############################%s\n",
+		 mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
+	return 0;
+}
+
+static void
+cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_mtu_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_mtu_config(res->dev, res->size);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
+	bool enable = false;
+	int rc = -EINVAL;
+
+	if (!strcmp(res->enable, "on"))
+		enable = true;
+
+	rc = ethdev_prom_mode_config(res->dev, enable);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip4_cmd_tokens *res = parsed_result;
+	struct ipv4_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip4_read(&config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip4_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_ip6_cmd_tokens *res = parsed_result;
+	struct ipv6_addr_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	rc = ethdev_ip6_addr_add(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_show_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_stats_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_stats_show(res->dev);
+	if (rc < 0)
+		printf(MSG_ARG_INVALID, res->dev);
+}
+
+static void
+cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_cmd_tokens *res = parsed_result;
+	struct ethdev_config config;
+	int rc;
+
+	memset(&config, 0, sizeof(struct ethdev_config));
+	config.rx.n_queues = res->nb_rxq;
+	config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
+	memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
+
+	config.tx.n_queues = res->nb_txq;
+	config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
+
+	config.mtu = port_conf_default.rxmode.mtu;
+
+	rc = ethdev_process(res->dev, &config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- ethdev command help -----------------------------",
+		 cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
+		 cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_stats_help,
+		 cmd_ethdev_show_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t ethdev_stats_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_stats_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_stats_stats =
+	TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
+
+cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
+	.f = cli_ethdev_stats,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_stats_cmd,
+		(void *)&ethdev_stats_dev,
+		(void *)&ethdev_stats_stats,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_show_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_show_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_show_show =
+	TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t ethdev_show_cmd_ctx = {
+	.f = cli_ethdev_show,
+	.data = NULL,
+	.help_str = cmd_ethdev_show_help,
+	.tokens = {
+		(void *)&ethdev_show_cmd,
+		(void *)&ethdev_show_dev,
+		(void *)&ethdev_show_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_mtu_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_mtu_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_mtu_mtu =
+	TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
+cmdline_parse_token_num_t ethdev_mtu_size =
+	TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
+
+cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
+	.f = cli_ethdev_mtu,
+	.data = NULL,
+	.help_str = cmd_ethdev_mtu_help,
+	.tokens = {
+		(void *)&ethdev_mtu_cmd,
+		(void *)&ethdev_mtu_dev,
+		(void *)&ethdev_mtu_mtu,
+		(void *)&ethdev_mtu_size,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_prom_mode_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_prom_mode_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_prom_mode_prom =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
+cmdline_parse_token_string_t ethdev_prom_mode_enable =
+	TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
+
+cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
+	.f = cli_ethdev_prom_mode,
+	.data = NULL,
+	.help_str = cmd_ethdev_prom_mode_help,
+	.tokens = {
+		(void *)&ethdev_prom_mode_cmd,
+		(void *)&ethdev_prom_mode_dev,
+		(void *)&ethdev_prom_mode_prom,
+		(void *)&ethdev_prom_mode_enable,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip4_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip4_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
+cmdline_parse_token_string_t ethdev_ip4_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip4_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip4_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip4_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip4_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
+	.f = cli_ip4_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip4_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip4_cmd,
+		(void *)&ethdev_ip4_dev,
+		(void *)&ethdev_ip4_ip4,
+		(void *)&ethdev_ip4_addr,
+		(void *)&ethdev_ip4_add,
+		(void *)&ethdev_ip4_ip,
+		(void *)&ethdev_ip4_netmask,
+		(void *)&ethdev_ip4_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_ip6_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_ip6_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_ip6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
+cmdline_parse_token_string_t ethdev_ip6_addr =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
+cmdline_parse_token_string_t ethdev_ip6_add =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ethdev_ip6_ip =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ethdev_ip6_netmask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ethdev_ip6_mask =
+	TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
+
+cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
+	.f = cli_ip6_addr,
+	.data = NULL,
+	.help_str = cmd_ethdev_ip6_addr_help,
+	.tokens = {
+		(void *)&ethdev_ip6_cmd,
+		(void *)&ethdev_ip6_dev,
+		(void *)&ethdev_ip6_ip6,
+		(void *)&ethdev_ip6_addr,
+		(void *)&ethdev_ip6_add,
+		(void *)&ethdev_ip6_ip,
+		(void *)&ethdev_ip6_netmask,
+		(void *)&ethdev_ip6_mask,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
+cmdline_parse_token_string_t ethdev_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rxq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
+cmdline_parse_token_num_t ethdev_nb_rxq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_txq =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
+cmdline_parse_token_num_t ethdev_nb_txq =
+	TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
+cmdline_parse_token_string_t ethdev_mempool =
+	TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
+
+cmdline_parse_inst_t ethdev_cmd_ctx = {
+	.f = cli_ethdev,
+	.data = NULL,
+	.help_str = cmd_ethdev_help,
+	.tokens = {
+		(void *)&ethdev_cmd,
+		(void *)&ethdev_dev,
+		(void *)&ethdev_rxq,
+		(void *)&ethdev_nb_rxq,
+		(void *)&ethdev_txq,
+		(void *)&ethdev_nb_txq,
+		(void *)&ethdev_mempool,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t ethdev_help_ethdev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
+
+cmdline_parse_inst_t ethdev_help_cmd_ctx = {
+	.f = cli_ethdev_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_help_cmd,
+		(void *)&ethdev_help_ethdev,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
new file mode 100644
index 0000000000..94d3247a2c
--- /dev/null
+++ b/app/graph/ethdev.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_H
+#define APP_GRAPH_ETHDEV_H
+
+#include <cmdline_parse.h>
+
+#define ETHDEV_IPV6_ADDR_LEN	16
+
+extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
+
+struct ipv4_addr_config {
+	uint32_t ip;
+	uint32_t mask;
+};
+
+struct ipv6_addr_config {
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
+};
+
+extern uint32_t enabled_port_mask;
+
+void ethdev_start(void);
+void ethdev_stop(void);
+void *ethdev_mempool_list_by_portid(uint16_t portid);
+int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
+int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
+void ethdev_list_clean(void);
+
+#endif
diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
new file mode 100644
index 0000000000..f231f3f3e1
--- /dev/null
+++ b/app/graph/ethdev_priv.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_PRIV_H
+#define APP_GRAPH_ETHDEV_PRIV_H
+
+#include "ethdev.h"
+
+#define NS_PER_SEC 1E9
+
+#define ETHDEV_RXQ_RSS_MAX	16
+#define ETHDEV_RX_DESC_DEFAULT 1024
+#define ETHDEV_TX_DESC_DEFAULT 1024
+
+struct ethdev_show_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t show;
+};
+
+struct ethdev_stats_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t stats;
+};
+
+struct ethdev_mtu_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t mtu;
+	uint16_t size;
+};
+
+struct ethdev_prom_mode_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t prom;
+	cmdline_fixed_string_t enable;
+};
+
+struct ethdev_ip4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_ip6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t addr;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+};
+
+struct ethdev_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t rxq;
+	cmdline_fixed_string_t txq;
+	cmdline_fixed_string_t mempool;
+	uint16_t nb_rxq;
+	uint16_t nb_txq;
+};
+
+struct ethdev_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t ethdev;
+};
+
+struct ethdev_rss_config {
+	uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
+	uint32_t n_queues;
+};
+
+struct ethdev_config {
+	char dev_name[RTE_ETH_NAME_MAX_LEN];
+	uint16_t port_id;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+		char mempool_name[RTE_MEMPOOL_NAMESIZE];
+		struct rte_mempool *mp;
+		struct ethdev_rss_config *rss;
+	} rx;
+
+	struct {
+		uint32_t n_queues;
+		uint32_t queue_size;
+	} tx;
+
+	int promiscuous;
+	uint32_t mtu;
+};
+
+struct ethdev {
+	TAILQ_ENTRY(ethdev) next;
+	struct ethdev_config config;
+	struct ipv4_addr_config ip4_addr;
+	struct ipv6_addr_config ip6_addr;
+};
+TAILQ_HEAD(ethdev_head, ethdev);
+#endif
diff --git a/app/graph/main.c b/app/graph/main.c
index 96548f49e7..c1cb435588 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -215,6 +215,7 @@ main(int argc, char **argv)
 
 exit:
 	conn_free(conn);
+	ethdev_stop();
 	cli_exit();
 	rte_eal_cleanup();
 	return 0;
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 5dc23c875b..c17e0cc63e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index b45419811b..e8a6ccb562 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -10,6 +10,7 @@
 
 #include "cli.h"
 #include "conn.h"
+#include "ethdev.h"
 #include "mempool.h"
 #include "utils.h"
 /*
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 6009b0c291..5dedea97de 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -78,7 +78,39 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
-   |                                      |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> rxq <n_queues>| | Command to create DPDK port with|   No    |    No    |
+   | | txq <n_queues> <mempool_name>      | | given number of Rx and Tx queues|         |          |
+   |                                      | | . Also attach RxQ with given    |         |          |
+   |                                      | | mempool. Each port can have     |         |          |
+   |                                      | | single mempool only i.e. all    |         |          |
+   |                                      | | RxQs will share the same mempool|         |          |
+   |                                      | | .                               |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> mtu <mtu_sz>    | | Command to configure MTU of DPDK|   Yes   |    Yes   |
+   |                                      | | port.                           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   |  | ethdev <ethdev_name> promiscuous  | | Command to enable/disable       |   Yes   |    Yes   |
+   |  | <on/off>                          | | promiscuous mode on DPDK port.  |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> show            | | Command to dump current ethdev  |   Yes   |    Yes   |
+   |                                      | | configuration.                  |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | ethdev <ethdev_name> stats           | | Command to dump current ethdev  |   Yes   |    Yes   |
+   |                                      | | statistics.                     |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> ip4 addr add  | | Command to configure IPv4       |   Yes   |    Yes   |
+   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
+   |                                      | | is needed if user wishes to use |         |          |
+   |                                      | | ``ipv4_lookup`` node.           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev <ethdev_name> ip6 addr add  | | Command to configure IPv6       |   Yes   |    Yes   |
+   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
+   |                                      | | is needed if user wishes to use |         |          |
+   |                                      | | ``ipv6_lookup`` node.           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
@@ -113,6 +145,22 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
 
    graph>
    graph>
+   graph> help ethdev
+
+   ----------------------------- ethdev command help -----------------------------
+   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
+   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
+   ethdev <ethdev_name> promiscuous <on/off>
+   ethdev <ethdev_name> mtu <mtu_sz>
+   ethdev <ethdev_name> stats
+   ethdev <ethdev_name> show
+   graph>
+
+To exit the telnet session, type ``Ctrl + ]``. This changes the ``graph>`` command prompt to
+``telnet>`` command prompt. Now running ``close`` or ``quit`` command on ``telnet>`` prompt
+will terminate the telnet session.
+
 Created graph for use case
 --------------------------
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 06/12] app/graph: support IPv4 lookup command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (4 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 05/12] app/graph: support ethdev " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:04                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 07/12] app/graph: support IPv6 " skori
                                                     ` (7 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds ipv4_lookup module to configure LPM table. This LPM table
will be used for IPv4 lookup and forwarding.

Following commands are exposed:
 - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
 - help ipv4_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   2 +-
 app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/route.h          |  26 +++++
 app/graph/route_priv.h     |  44 ++++++++
 doc/guides/tools/graph.rst |   9 ++
 8 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/ip4_route.c
 create mode 100644 app/graph/route.h
 create mode 100644 app/graph/route_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index fa394fade6..25785ea4dc 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 8df55b4b12..4e4d23b692 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -164,7 +164,7 @@ ethdev_stop(void)
 	}
 
 	ethdev_list_clean();
-	rte_eal_cleanup();
+	route_ip4_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
new file mode 100644
index 0000000000..db3354c270
--- /dev/null
+++ b/app/graph/ip4_route.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_node_ip4_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
+
+struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
+
+
+void
+route_ip4_list_clean(void)
+{
+	struct route_ipv4_config *route;
+
+	while (!TAILQ_EMPTY(&route4)) {
+		route = TAILQ_FIRST(&route4);
+		TAILQ_REMOVE(&route4, route, next);
+	}
+}
+
+static struct route_ipv4_config *
+find_route4_entry(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+
+	TAILQ_FOREACH(ipv4route, &route4, next) {
+		if (!memcmp(ipv4route, route, sizeof(*route)))
+			return ipv4route;
+	}
+	return NULL;
+
+}
+
+static uint8_t
+convert_netmask_to_depth(uint32_t netmask)
+{
+	uint8_t zerobits = 0;
+
+	while ((netmask & 0x1) == 0) {
+		netmask = netmask >> 1;
+		zerobits++;
+	}
+
+	return (32 - zerobits);
+}
+
+static int
+route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+
+	depth = convert_netmask_to_depth(ipv4route->netmask);
+
+	return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
+			RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
+}
+
+static int
+route_ip4_add(struct route_ipv4_config *route)
+{
+	struct route_ipv4_config *ipv4route;
+	int rc = -EINVAL;
+
+	ipv4route = find_route4_entry(route);
+
+	if (!ipv4route) {
+		ipv4route = malloc(sizeof(struct route_ipv4_config));
+		if (!ipv4route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	ipv4route->ip = route->ip;
+	ipv4route->netmask = route->netmask;
+	ipv4route->via = route->via;
+	ipv4route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route4_rewirte_table_update(ipv4route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
+	return 0;
+free:
+	free(ipv4route);
+	return rc;
+}
+
+int
+route_ip4_add_to_lookup(void)
+{
+	struct route_ipv4_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route4, next) {
+		rc = route4_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv4_lookup command help ---------------------------",
+		 cmd_ipv4_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip4_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv4_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip4_read(&config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv4");
+		return;
+	}
+
+	if (parser_ip4_read(&config.netmask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip4_read(&config.via, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "via ip");
+		return;
+	}
+
+	rc = route_ip4_add(&config);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip4_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
+cmdline_parse_token_string_t ip4_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip4_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip4_lookup_ip4 =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t ip4_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip4_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip4_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip4_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip4_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
+	.f = cli_ipv4_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv4_lookup_help,
+	.tokens = {
+		(void *)&ip4_lookup_cmd,
+		(void *)&ip4_lookup_route,
+		(void *)&ip4_lookup_add,
+		(void *)&ip4_lookup_ip4,
+		(void *)&ip4_lookup_ip,
+		(void *)&ip4_lookup_netmask,
+		(void *)&ip4_lookup_mask,
+		(void *)&ip4_lookup_via,
+		(void *)&ip4_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv4_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv4_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
+
+cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
+	.f = cli_ipv4_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv4_lookup_help_cmd,
+		(void *)&ipv4_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index c17e0cc63e..1f35f82583 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -13,6 +13,7 @@ sources = files(
         'cli.c',
         'conn.c',
         'ethdev.c',
+        'ip4_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e8a6ccb562..bd4d245c75 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "route.h"
 #include "utils.h"
 /*
  * Externs
diff --git a/app/graph/route.h b/app/graph/route.h
new file mode 100644
index 0000000000..a44d401d55
--- /dev/null
+++ b/app/graph/route.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_H
+#define APP_GRAPH_ROUTE_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+
+struct route_ipv4_config {
+	TAILQ_ENTRY(route_ipv4_config) next;
+	uint32_t ip;
+	uint32_t netmask;
+	uint32_t via;
+	bool is_used;
+};
+
+TAILQ_HEAD(ip4_route, route_ipv4_config);
+
+int route_ip4_add_to_lookup(void);
+void route_ip4_list_clean(void);
+
+#endif
diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
new file mode 100644
index 0000000000..f363a551a9
--- /dev/null
+++ b/app/graph/route_priv.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ROUTE_PRIV_H
+#define APP_GRAPH_ROUTE_PRIV_H
+
+#define MAX_ROUTE_ENTRIES 32
+
+struct ip4_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ip6_lookup_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t route;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t netmask;
+	cmdline_fixed_string_t mask;
+	cmdline_fixed_string_t via;
+	cmdline_fixed_string_t via_ip;
+};
+
+struct ipv4_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct ipv6_lookup_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 5dedea97de..7530ef6f65 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -112,6 +112,15 @@ file to express the requested use case configuration.
    | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ipv4_lookup route add ipv4 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
+   | |  netmask <mask> via <ip>           | | ``ipv4_lookup`` LPM table. It is|         |          |
+   |                                      | | needed if user wishes to route  |         |          |
+   |                                      | | the packets based on LPM lookup |         |          |
+   |                                      | | table.                          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
+   |                                      | | help message.                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 07/12] app/graph: support IPv6 lookup command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (5 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 06/12] app/graph: support IPv4 lookup " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:04                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 08/12] app/graph: support neigh " skori
                                                     ` (6 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ipv6_lookup module to configure LPM6 table. This LPM6 table
will be used for IPv6 lookup and forwarding.

Following commands are exposed:
 - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
 - help ipv6_lookup

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev.c         |   1 +
 app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
 app/graph/meson.build      |   1 +
 app/graph/route.h          |  14 +++
 doc/guides/tools/graph.rst |   9 ++
 6 files changed, 253 insertions(+)
 create mode 100644 app/graph/ip6_route.c

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 25785ea4dc..1280422388 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
+	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index 4e4d23b692..e3f9ee3e0c 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -165,6 +165,7 @@ ethdev_stop(void)
 
 	ethdev_list_clean();
 	route_ip4_list_clean();
+	route_ip6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
new file mode 100644
index 0000000000..e793cde830
--- /dev/null
+++ b/app/graph/ip6_route.c
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+
+#include <rte_node_ip6_api.h>
+
+#include "module_api.h"
+#include "route_priv.h"
+
+static const char
+cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
+
+struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
+
+void
+route_ip6_list_clean(void)
+{
+	struct route_ipv6_config *route;
+
+	while (!TAILQ_EMPTY(&route6)) {
+		route = TAILQ_FIRST(&route6);
+		TAILQ_REMOVE(&route6, route, next);
+	}
+}
+
+static struct route_ipv6_config *
+find_route6_entry(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+
+	TAILQ_FOREACH(ipv6route, &route6, next) {
+		if (!memcmp(ipv6route, route, sizeof(*route)))
+			return ipv6route;
+	}
+	return NULL;
+}
+
+static uint8_t
+convert_ip6_netmask_to_depth(uint8_t *netmask)
+{
+	uint8_t setbits = 0;
+	uint8_t mask;
+	int i;
+
+	for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
+		mask = netmask[i];
+		while (mask & 0x80) {
+			mask = mask << 1;
+			setbits++;
+		}
+	}
+
+	return setbits;
+}
+
+static int
+route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
+{
+	uint8_t depth;
+	int portid;
+
+	portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
+	if (portid < 0) {
+		printf("Invalid portid found to install the route\n");
+		return portid;
+	}
+	depth = convert_ip6_netmask_to_depth(ipv6route->mask);
+
+	return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
+			RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
+
+}
+
+static int
+route_ip6_add(struct route_ipv6_config *route)
+{
+	struct route_ipv6_config *ipv6route;
+	int rc = -EINVAL;
+	int j;
+
+	ipv6route = find_route6_entry(route);
+	if (!ipv6route) {
+		ipv6route = malloc(sizeof(struct route_ipv6_config));
+		if (!ipv6route)
+			return -ENOMEM;
+	} else {
+		return 0;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
+		ipv6route->ip[j] = route->ip[j];
+		ipv6route->mask[j] = route->mask[j];
+		ipv6route->gateway[j] = route->gateway[j];
+	}
+	ipv6route->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = route6_rewirte_table_update(ipv6route);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
+	return 0;
+free:
+	free(ipv6route);
+	return rc;
+}
+
+int
+route_ip6_add_to_lookup(void)
+{
+	struct route_ipv6_config *route = NULL;
+	int rc = -EINVAL;
+
+	TAILQ_FOREACH(route, &route6, next) {
+		rc = route6_rewirte_table_update(route);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		     __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "--------------------------- ipv6_lookup command help ---------------------------",
+		 cmd_ipv6_lookup_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ip6_lookup_cmd_tokens *res = parsed_result;
+	struct route_ipv6_config config;
+	int rc = -EINVAL;
+
+	if (parser_ip6_read(config.ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ipv6");
+		return;
+	}
+
+	if (parser_ip6_read(config.mask, res->mask)) {
+		printf(MSG_ARG_INVALID, "netmask");
+		return;
+	}
+
+	if (parser_ip6_read(config.gateway, res->via_ip)) {
+		printf(MSG_ARG_INVALID, "gateway ip");
+		return;
+	}
+
+	rc = route_ip6_add(&config);
+	if (rc)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+cmdline_parse_token_string_t ip6_lookup_cmd =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
+cmdline_parse_token_string_t ip6_lookup_route =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
+cmdline_parse_token_string_t ip6_lookup_add =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
+cmdline_parse_token_string_t ip6_lookup_ip6 =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t ip6_lookup_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t ip6_lookup_netmask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
+cmdline_parse_token_string_t ip6_lookup_mask =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
+cmdline_parse_token_string_t ip6_lookup_via =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
+cmdline_parse_token_string_t ip6_lookup_via_ip =
+	TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
+
+cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
+	.f = cli_ipv6_lookup,
+	.data = NULL,
+	.help_str = cmd_ipv6_lookup_help,
+	.tokens = {
+		(void *)&ip6_lookup_cmd,
+		(void *)&ip6_lookup_route,
+		(void *)&ip6_lookup_add,
+		(void *)&ip6_lookup_ip6,
+		(void *)&ip6_lookup_ip,
+		(void *)&ip6_lookup_netmask,
+		(void *)&ip6_lookup_mask,
+		(void *)&ip6_lookup_via,
+		(void *)&ip6_lookup_via_ip,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ipv6_lookup_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ipv6_lookup_help_module =
+	TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
+
+cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
+	.f = cli_ipv6_lookup_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ipv6_lookup_help_cmd,
+		(void *)&ipv6_lookup_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 1f35f82583..413bbefc4e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev.c',
         'ip4_route.c',
+        'ip6_route.c',
         'main.c',
         'mempool.c',
         'utils.c',
diff --git a/app/graph/route.h b/app/graph/route.h
index a44d401d55..0d271d1350 100644
--- a/app/graph/route.h
+++ b/app/graph/route.h
@@ -8,7 +8,9 @@
 #define MAX_ROUTE_ENTRIES 32
 
 extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
 extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
+extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
 
 struct route_ipv4_config {
 	TAILQ_ENTRY(route_ipv4_config) next;
@@ -20,7 +22,19 @@ struct route_ipv4_config {
 
 TAILQ_HEAD(ip4_route, route_ipv4_config);
 
+struct route_ipv6_config {
+	TAILQ_ENTRY(route_ipv6_config) next;
+	uint8_t ip[16];
+	uint8_t mask[16];
+	uint8_t gateway[16];
+	bool is_used;
+};
+
+TAILQ_HEAD(ip6_route, route_ipv6_config);
+
 int route_ip4_add_to_lookup(void);
+int route_ip6_add_to_lookup(void);
 void route_ip4_list_clean(void);
+void route_ip6_list_clean(void);
 
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 7530ef6f65..56c2eaad26 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -121,6 +121,15 @@ file to express the requested use case configuration.
    | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
    |                                      | | help message.                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ipv6_lookup route add ipv6 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
+   | |  netmask <mask> via <ip>           | | ``ipv6_lookup`` LPM table. It is|         |          |
+   |                                      | | needed if user wishes to route  |         |          |
+   |                                      | | the packets based on LPM6 lookup|         |          |
+   |                                      | | table.                          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
+   |                                      | | help message.                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 08/12] app/graph: support neigh command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (6 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 07/12] app/graph: support IPv6 " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:05                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 09/12] app/graph: support ethdev Rx " skori
                                                     ` (5 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Sunil Kumar Kori <skori@marvell.com>

Adds neigh module to configure arp/neigh. This module uses
ipv4_rewrite and ipv6_rewrite node to write neigh information.

Following commands are exposed:
 - neigh add ipv4 <ip> <mac>
 - neigh add ipv6 <ip> <mac>
 - help neigh

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   3 +
 app/graph/ethdev.c         |   2 +
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   2 +
 app/graph/neigh.c          | 358 +++++++++++++++++++++++++++++++++++++
 app/graph/neigh.h          |  17 ++
 app/graph/neigh_priv.h     |  49 +++++
 doc/guides/tools/graph.rst |  11 ++
 8 files changed, 443 insertions(+)
 create mode 100644 app/graph/neigh.c
 create mode 100644 app/graph/neigh.h
 create mode 100644 app/graph/neigh_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index 1280422388..f564362da1 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
+	(cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
 	NULL,
 };
 
diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
index e3f9ee3e0c..c9b09168c1 100644
--- a/app/graph/ethdev.c
+++ b/app/graph/ethdev.c
@@ -166,6 +166,8 @@ ethdev_stop(void)
 	ethdev_list_clean();
 	route_ip4_list_clean();
 	route_ip6_list_clean();
+	neigh4_list_clean();
+	neigh6_list_clean();
 	printf("Bye...\n");
 }
 
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 413bbefc4e..8fa9d605b9 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,5 +17,6 @@ sources = files(
         'ip6_route.c',
         'main.c',
         'mempool.c',
+        'neigh.c',
         'utils.c',
 )
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index bd4d245c75..e9e42da7cc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,8 +12,10 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "mempool.h"
+#include "neigh.h"
 #include "route.h"
 #include "utils.h"
+
 /*
  * Externs
  */
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
new file mode 100644
index 0000000000..0cee502719
--- /dev/null
+++ b/app/graph/neigh.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_node_ip4_api.h>
+#include <rte_node_ip6_api.h>
+
+#include "neigh_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
+
+static const char
+cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
+
+struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
+struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
+
+void
+neigh4_list_clean(void)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	while (!TAILQ_EMPTY(&neigh4)) {
+		v4_config = TAILQ_FIRST(&neigh4);
+		TAILQ_REMOVE(&neigh4, v4_config, next);
+	}
+}
+
+void
+neigh6_list_clean(void)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	while (!TAILQ_EMPTY(&neigh6)) {
+		v6_config = TAILQ_FIRST(&neigh6);
+		TAILQ_REMOVE(&neigh6, v6_config, next);
+	}
+}
+
+static struct neigh_ipv4_config *
+find_neigh4_entry(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+
+	TAILQ_FOREACH(v4_config, &neigh4, next) {
+		if ((v4_config->ip == ip) && (v4_config->mac == mac))
+			return v4_config;
+	}
+	return NULL;
+}
+
+static struct neigh_ipv6_config *
+find_neigh6_entry(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+
+	TAILQ_FOREACH(v6_config, &neigh6, next) {
+		if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
+			return v6_config;
+	}
+	return NULL;
+}
+
+static int
+ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
+	if (portid < 0) {
+		printf("Invalid portid found to add neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0)
+		return rc;
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip6_rewrite_add(portid, data, len, portid);
+}
+
+static int
+ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
+{
+	uint8_t data[2 * RTE_ETHER_ADDR_LEN];
+	uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
+	struct rte_ether_addr smac;
+	int16_t portid = 0;
+	int rc;
+
+	portid = ethdev_portid_by_ip4(v4_config->ip, 0);
+	if (portid < 0) {
+		printf("Invalid portid found to add  neigh\n");
+		return -EINVAL;
+	}
+
+	memset(data, 0, len);
+
+	/* Copy dst mac */
+	rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
+
+	/* Copy src mac */
+	rc = rte_eth_macaddr_get(portid, &smac);
+	if (rc < 0) {
+		printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
+		return rc;
+	}
+
+	rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
+
+	return rte_node_ip4_rewrite_add(portid, data, len, portid);
+}
+
+
+static int
+neigh_ip4_add(uint32_t ip, uint64_t mac)
+{
+	struct neigh_ipv4_config *v4_config;
+	int rc = -EINVAL;
+
+	v4_config = find_neigh4_entry(ip, mac);
+
+	if (!v4_config) {
+		v4_config = malloc(sizeof(struct neigh_ipv4_config));
+		if (!v4_config)
+			return -ENOMEM;
+	}
+
+	v4_config->ip = ip;
+	v4_config->mac = mac;
+	v4_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc = ip4_rewrite_node_add(v4_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
+	return 0;
+free:
+	free(v4_config);
+	return rc;
+}
+
+static int
+neigh_ip6_add(uint8_t *ip, uint64_t mac)
+{
+	struct neigh_ipv6_config *v6_config;
+	int rc = -EINVAL;
+	int j;
+
+	v6_config = find_neigh6_entry(ip, mac);
+
+	if (!v6_config) {
+		v6_config = malloc(sizeof(struct neigh_ipv6_config));
+		if (!v6_config)
+			return -ENOMEM;
+	}
+
+	for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
+		v6_config->ip[j] = ip[j];
+
+	v6_config->mac = mac;
+	v6_config->is_used = true;
+
+	/* FIXME: Get graph status here and then update table */
+	rc =  ip6_rewrite_node_add(v6_config);
+	if (rc)
+		goto free;
+
+	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
+	return 0;
+free:
+	free(v6_config);
+	return rc;
+}
+
+int
+neigh_ip4_add_to_rewrite(void)
+{
+	struct neigh_ipv4_config *neigh;
+	int rc;
+
+	TAILQ_FOREACH(neigh, &neigh4, next) {
+		rc = ip4_rewrite_node_add(neigh);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+int
+neigh_ip6_add_to_rewrite(void)
+{
+	struct neigh_ipv6_config *neigh;
+	int rc;
+
+
+	TAILQ_FOREACH(neigh, &neigh6, next) {
+		rc = ip6_rewrite_node_add(neigh);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void
+cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v4_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+	uint64_t mac;
+	uint32_t ip;
+
+	if (parser_ip4_read(&ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip4_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct neigh_v6_cmd_tokens *res = parsed_result;
+	uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
+	int rc = -EINVAL;
+	uint64_t mac;
+
+	if (parser_ip6_read(ip, res->ip)) {
+		printf(MSG_ARG_INVALID, "ip");
+		return;
+	}
+
+	if (parser_mac_read(&mac, res->mac)) {
+		printf(MSG_ARG_INVALID, "mac");
+		return;
+	}
+
+	rc = neigh_ip6_add(ip, mac);
+	if (rc < 0)
+		printf(MSG_CMD_FAIL, res->cmd);
+}
+
+static void
+cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
+		 "--------------------------- neigh command help ---------------------------",
+		 cmd_neigh_v4_help, cmd_neigh_v6_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t neigh_v4_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v4_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v4_ip4 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
+cmdline_parse_token_string_t neigh_v4_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v4_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v4_cmd_ctx = {
+	.f = cli_neigh_v4,
+	.data = NULL,
+	.help_str = cmd_neigh_v4_help,
+	.tokens = {
+		(void *)&neigh_v4_cmd,
+		(void *)&neigh_v4_add,
+		(void *)&neigh_v4_ip4,
+		(void *)&neigh_v4_ip,
+		(void *)&neigh_v4_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_v6_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
+cmdline_parse_token_string_t neigh_v6_add =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
+cmdline_parse_token_string_t neigh_v6_ip6 =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
+cmdline_parse_token_string_t neigh_v6_ip =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
+cmdline_parse_token_string_t neigh_v6_mac =
+	TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
+
+cmdline_parse_inst_t neigh_v6_cmd_ctx = {
+	.f = cli_neigh_v6,
+	.data = NULL,
+	.help_str = cmd_neigh_v6_help,
+	.tokens = {
+		(void *)&neigh_v6_cmd,
+		(void *)&neigh_v6_add,
+		(void *)&neigh_v6_ip6,
+		(void *)&neigh_v6_ip,
+		(void *)&neigh_v6_mac,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t neigh_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t neigh_help_module =
+	TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
+
+cmdline_parse_inst_t neigh_help_cmd_ctx = {
+	.f = cli_neigh_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&neigh_help_cmd,
+		(void *)&neigh_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/neigh.h b/app/graph/neigh.h
new file mode 100644
index 0000000000..928981fc31
--- /dev/null
+++ b/app/graph/neigh.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_H
+#define APP_GRAPH_NEIGH_H
+
+extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
+extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
+extern cmdline_parse_inst_t neigh_help_cmd_ctx;
+
+void neigh4_list_clean(void);
+void neigh6_list_clean(void);
+int neigh_ip4_add_to_rewrite(void);
+int neigh_ip6_add_to_rewrite(void);
+
+#endif
diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
new file mode 100644
index 0000000000..0ec9b1510f
--- /dev/null
+++ b/app/graph/neigh_priv.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_NEIGH_PRIV_H
+#define APP_GRAPH_NEIGH_PRIV_H
+
+#define MAX_NEIGH_ENTRIES 32
+
+struct neigh_v4_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip4;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_v6_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t add;
+	cmdline_fixed_string_t ip6;
+	cmdline_fixed_string_t ip;
+	cmdline_fixed_string_t mac;
+};
+
+struct neigh_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct neigh_ipv4_config {
+	TAILQ_ENTRY(neigh_ipv4_config) next;
+	uint32_t ip;
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
+
+struct neigh_ipv6_config {
+	TAILQ_ENTRY(neigh_ipv6_config) next;
+	uint8_t ip[16];
+	uint64_t mac;
+	bool is_used;
+};
+
+TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
+
+#endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 56c2eaad26..b1bd2e6048 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -130,6 +130,17 @@ file to express the requested use case configuration.
    | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
    |                                      | | help message.                   |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | neigh add ipv4 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
+   |                                      | | information into                |         |          |
+   |                                      | | ``ipv4_rewrite`` node.          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | neigh add ipv6 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
+   |                                      | | information into                |         |          |
+   |                                      | | ``ipv6_rewrite`` node.          |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 09/12] app/graph: support ethdev Rx command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (7 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 08/12] app/graph: support neigh " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:05                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 10/12] app/graph: support graph " skori
                                                     ` (4 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds ethdev_rx module to create port-queue-core mapping.

Mapping will be used to launch graph worker thread and dequeue
packets on mentioned core from desired port/queue.

Following commands are exposed:
 - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
 - help ethdev_rx

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   2 +
 app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
 app/graph/ethdev_rx.h      |  37 +++++++++
 app/graph/ethdev_rx_priv.h |  39 +++++++++
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 doc/guides/tools/graph.rst |  10 +++
 7 files changed, 255 insertions(+)
 create mode 100644 app/graph/ethdev_rx.c
 create mode 100644 app/graph/ethdev_rx.h
 create mode 100644 app/graph/ethdev_rx_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index f564362da1..ad7d7deadf 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
 	(cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
+	(cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
new file mode 100644
index 0000000000..f2cb8cf9a5
--- /dev/null
+++ b/app/graph/ethdev_rx.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+
+#include "ethdev_rx_priv.h"
+#include "module_api.h"
+
+static const char
+cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
+
+static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+struct lcore_params *lcore_params = lcore_params_array;
+struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+uint16_t nb_lcore_params;
+
+static void
+rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
+{
+	uint8_t n_rx_queue;
+
+	n_rx_queue = lcore_conf[core].n_rx_queue;
+	lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
+	lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
+	lcore_conf[core].n_rx_queue++;
+}
+
+uint8_t
+ethdev_rx_num_rx_queues_get(uint16_t port)
+{
+	int queue = -1;
+	uint16_t i;
+
+	for (i = 0; i < nb_lcore_params; ++i) {
+		if (lcore_params[i].port_id == port) {
+			if (lcore_params[i].queue_id == queue + 1)
+				queue = lcore_params[i].queue_id;
+			else
+				rte_exit(EXIT_FAILURE,
+					 "Queue ids of the port %d must be"
+					 " in sequence and must start with 0\n",
+					 lcore_params[i].port_id);
+		}
+	}
+
+	return (uint8_t)(++queue);
+}
+
+static int
+ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
+{
+	uint64_t coremask;
+	uint16_t port_id;
+	int rc;
+
+	if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
+		return -EINVAL;
+
+	rc = rte_eth_dev_get_port_by_name(name, &port_id);
+	if (rc)
+		return -EINVAL;
+
+	coremask = 0xff; /* FIXME: Read from graph configuration */
+
+	if (!(coremask & (1 << core)))
+		return -EINVAL;
+
+	rx_map_configure(port_id, queue, core);
+
+	lcore_params_array[nb_lcore_params].port_id = port_id;
+	lcore_params_array[nb_lcore_params].queue_id = queue;
+	lcore_params_array[nb_lcore_params].lcore_id = core;
+	nb_lcore_params++;
+	return 0;
+}
+
+static void
+cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		   __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
+		 "----------------------------- ethdev_rx command help -----------------------------",
+		 cmd_ethdev_rx_help);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+static void
+cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
+{
+	struct ethdev_rx_cmd_tokens *res = parsed_result;
+	int rc = -EINVAL;
+
+	rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->cmd);
+		rte_exit(EXIT_FAILURE, "input core is Invalid\n");
+	}
+
+}
+
+cmdline_parse_token_string_t ethdev_rx_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
+cmdline_parse_token_string_t ethdev_rx_map =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
+cmdline_parse_token_string_t ethdev_rx_port =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
+cmdline_parse_token_string_t ethdev_rx_dev =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
+cmdline_parse_token_string_t ethdev_rx_queue =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
+cmdline_parse_token_num_t ethdev_rx_qid =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
+cmdline_parse_token_string_t ethdev_rx_core =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
+cmdline_parse_token_num_t ethdev_rx_core_id =
+	TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
+
+cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
+	.f = cli_ethdev_rx,
+	.data = NULL,
+	.help_str = cmd_ethdev_rx_help,
+	.tokens = {
+		(void *)&ethdev_rx_cmd,
+		(void *)&ethdev_rx_map,
+		(void *)&ethdev_rx_port,
+		(void *)&ethdev_rx_dev,
+		(void *)&ethdev_rx_queue,
+		(void *)&ethdev_rx_qid,
+		(void *)&ethdev_rx_core,
+		(void *)&ethdev_rx_core_id,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t ethdev_rx_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
+cmdline_parse_token_string_t ethdev_rx_help_module =
+	TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
+
+cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
+	.f = cli_ethdev_rx_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&ethdev_rx_help_cmd,
+		(void *)&ethdev_rx_help_module,
+		NULL,
+	},
+};
diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
new file mode 100644
index 0000000000..8e7b31448c
--- /dev/null
+++ b/app/graph/ethdev_rx.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_H
+#define APP_GRAPH_ETHDEV_RX_H
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
+#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
+
+struct lcore_rx_queue {
+	uint16_t port_id;
+	uint8_t queue_id;
+	char node_name[RTE_NODE_NAMESIZE];
+};
+
+struct lcore_conf {
+	uint16_t n_rx_queue;
+	struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
+	struct rte_graph *graph;
+	char name[RTE_GRAPH_NAMESIZE];
+	rte_graph_t graph_id;
+} __rte_cache_aligned;
+
+uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
+
+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
+extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
+extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
+extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
+extern struct lcore_params *lcore_params;
+extern uint16_t nb_lcore_params;
+
+#endif
diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
new file mode 100644
index 0000000000..5d155be043
--- /dev/null
+++ b/app/graph/ethdev_rx_priv.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
+#define APP_GRAPH_ETHDEV_RX_PRIV_H
+
+#include <stdint.h>
+
+#include <rte_graph.h>
+#include <rte_node_eth_api.h>
+
+#define MAX_RX_QUEUE_PER_PORT 128
+#define MAX_JUMBO_PKT_LEN  9600
+#define NB_SOCKETS 8
+
+struct ethdev_rx_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t map;
+	cmdline_fixed_string_t port;
+	cmdline_fixed_string_t dev;
+	cmdline_fixed_string_t queue;
+	cmdline_fixed_string_t core;
+	uint32_t core_id;
+	uint32_t qid;
+};
+
+struct ethdev_rx_help_cmd_tokens {
+	cmdline_fixed_string_t cmd;
+	cmdline_fixed_string_t module;
+};
+
+struct lcore_params {
+	uint16_t port_id;
+	uint8_t queue_id;
+	uint8_t lcore_id;
+} __rte_cache_aligned;
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 8fa9d605b9..d8391d5cae 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
 sources = files(
         'cli.c',
         'conn.c',
+        'ethdev_rx.c',
         'ethdev.c',
         'ip4_route.c',
         'ip6_route.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index e9e42da7cc..56b7c94ecc 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -11,6 +11,7 @@
 #include "cli.h"
 #include "conn.h"
 #include "ethdev.h"
+#include "ethdev_rx.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index b1bd2e6048..318d92a0fb 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -141,6 +141,16 @@ file to express the requested use case configuration.
    | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
    |                                      | | message.                        |         |          |
    +--------------------------------------+-----------------------------------+---------+----------+
+   | | ethdev_rx map port <ethdev_name>   | | Command to add port-queue-core  |   No    |    No    |
+   | | queue <q_num> core <core_id>       | | mapping to ``ethdev_rx`` node.  |         |          |
+   |                                      | | ``ethdev_rx`` node instance will|         |          |
+   |                                      | | be pinned on given core and will|         |          |
+   |                                      | | poll on requested port/queue    |         |          |
+   |                                      | | pair.                           |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help ethdev_rx                       | | Command to dump ethdev_rx help  |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
 
 Runtime configuration
 ---------------------
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 10/12] app/graph: support graph command line interfaces
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (8 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 09/12] app/graph: support ethdev Rx " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:06                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 11/12] app/graph: support CLI option to enable graph stats skori
                                                     ` (3 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds graph module to create a graph for a given use case like
l3fwd.

Following commands are exposed:
 - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
	model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
	pcap_file <output_capture_file>
 - graph start
 - graph stats show
 - help graph

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
 app/graph/cli.c            |   4 +
 app/graph/ethdev_rx.c      |   2 +-
 app/graph/graph.c          | 550 +++++++++++++++++++++++++++++++++++++
 app/graph/graph.h          |  21 ++
 app/graph/graph_priv.h     |  70 +++++
 app/graph/ip4_route.c      |   5 +-
 app/graph/ip6_route.c      |   5 +-
 app/graph/meson.build      |   1 +
 app/graph/module_api.h     |   1 +
 app/graph/neigh.c          |  10 +-
 doc/guides/tools/graph.rst |  19 ++
 11 files changed, 683 insertions(+), 5 deletions(-)
 create mode 100644 app/graph/graph.c
 create mode 100644 app/graph/graph.h
 create mode 100644 app/graph/graph_priv.h

diff --git a/app/graph/cli.c b/app/graph/cli.c
index ad7d7deadf..30b12312d6 100644
--- a/app/graph/cli.c
+++ b/app/graph/cli.c
@@ -20,6 +20,10 @@
 #define MAX_LINE_SIZE 2048
 
 cmdline_parse_ctx_t modules_ctx[] = {
+	(cmdline_parse_inst_t *)&graph_config_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_start_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
+	(cmdline_parse_inst_t *)&graph_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
 	(cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
 	(cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
index f2cb8cf9a5..03f8effcca 100644
--- a/app/graph/ethdev_rx.c
+++ b/app/graph/ethdev_rx.c
@@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
 	if (rc)
 		return -EINVAL;
 
-	coremask = 0xff; /* FIXME: Read from graph configuration */
+	coremask = graph_coremask_get();
 
 	if (!(coremask & (1 << core)))
 		return -EINVAL;
diff --git a/app/graph/graph.c b/app/graph/graph.c
new file mode 100644
index 0000000000..74a99dd68e
--- /dev/null
+++ b/app/graph/graph.c
@@ -0,0 +1,550 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <cmdline_socket.h>
+#include <rte_ethdev.h>
+#include <rte_graph_worker.h>
+#include <rte_log.h>
+
+#include "graph_priv.h"
+#include "module_api.h"
+
+#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
+
+static const char
+cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
+		   "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
+		   "pcap_file <output_capture_file>";
+
+static const char * const supported_usecases[] = {"l3fwd"};
+struct graph_config graph_config;
+bool graph_started;
+
+/* Check the link rc of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
+	char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
+	uint8_t count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+	uint16_t portid;
+	int rc;
+
+	printf("\nChecking link status...");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		if (force_quit)
+			return;
+
+		all_ports_up = 1;
+		RTE_ETH_FOREACH_DEV(portid)
+		{
+			if (force_quit)
+				return;
+
+			if ((port_mask & (1 << portid)) == 0)
+				continue;
+
+			memset(&link, 0, sizeof(link));
+			rc = rte_eth_link_get_nowait(portid, &link);
+			if (rc < 0) {
+				all_ports_up = 0;
+				if (print_flag == 1)
+					printf("Port %u link get failed: %s\n",
+					       portid, rte_strerror(-rc));
+				continue;
+			}
+
+			/* Print link rc if flag set */
+			if (print_flag == 1) {
+				rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
+					&link);
+				printf("Port %d %s\n", portid, link_rc_text);
+				continue;
+			}
+
+			/* Clear all_ports_up flag if any link down */
+			if (link.link_status == RTE_ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+
+		/* After finally printing all link rc, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* Set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("Done\n");
+		}
+	}
+}
+
+static bool
+parser_usecases_read(char *usecases)
+{
+	bool valid = false;
+	uint32_t i, j = 0;
+	char *token;
+
+	token = strtok(usecases, ",");
+	while (token != NULL) {
+		for (i = 0; i < RTE_DIM(supported_usecases); i++) {
+			if (strcmp(supported_usecases[i], token) == 0) {
+				graph_config.usecases[j].enabled = true;
+				rte_strscpy(graph_config.usecases[j].name, token, 31);
+				valid = true;
+				j++;
+				break;
+			}
+		}
+		token = strtok(NULL, ",");
+	}
+
+	return valid;
+}
+
+static uint64_t
+graph_worker_count_get(void)
+{
+	uint64_t nb_worker = 0;
+	uint64_t coremask;
+
+	coremask = graph_config.params.coremask;
+	while (coremask) {
+		if (coremask & 0x1)
+			nb_worker++;
+
+		coremask = (coremask >> 1);
+	}
+
+	return nb_worker;
+}
+
+static struct rte_node_ethdev_config *
+graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
+{
+	uint32_t n_tx_queue, nb_conf = 0, lcore_id;
+	uint16_t queueid, portid, nb_graphs = 0;
+	uint8_t nb_rx_queue, queue;
+	struct lcore_conf *qconf;
+
+	n_tx_queue = graph_worker_count_get();
+	if (n_tx_queue > RTE_MAX_ETHPORTS)
+		n_tx_queue = RTE_MAX_ETHPORTS;
+
+	RTE_ETH_FOREACH_DEV(portid) {
+		/* Skip ports that are not enabled */
+		if ((enabled_port_mask & (1 << portid)) == 0) {
+			printf("\nSkipping disabled port %d\n", portid);
+			continue;
+		}
+
+		nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
+
+		/* Setup ethdev node config */
+		ethdev_conf[nb_conf].port_id = portid;
+		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
+		ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
+		ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
+		ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
+
+		nb_conf++;
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
+		fflush(stdout);
+
+		/* Init RX queues */
+		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
+			portid = qconf->rx_queue_list[queue].port_id;
+			queueid = qconf->rx_queue_list[queue].queue_id;
+
+			/* Add this queue node to its graph */
+			snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
+				 "ethdev_rx-%u-%u", portid, queueid);
+		}
+		if (qconf->n_rx_queue)
+			nb_graphs++;
+	}
+
+	printf("\n");
+
+	ethdev_start();
+	check_all_ports_link_status(enabled_port_mask);
+
+	*num_conf = nb_conf;
+	*num_graphs = nb_graphs;
+	return ethdev_conf;
+}
+
+static void
+graph_stats_print_to_file(void)
+{
+	struct rte_graph_cluster_stats_param s_param;
+	struct rte_graph_cluster_stats *stats;
+	const char *pattern = "worker_*";
+	FILE *fp = NULL;
+	size_t sz, len;
+
+	/* Prepare stats object */
+	fp = fopen("/tmp/graph_stats.txt", "w+");
+	if (fp == NULL)
+		rte_exit(EXIT_FAILURE, "Error in opening stats file\n");
+
+	memset(&s_param, 0, sizeof(s_param));
+	s_param.f = fp;
+	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");
+
+	/* Clear screen and move to top left */
+	rte_graph_cluster_stats_get(stats, 0);
+	rte_delay_ms(1E3);
+
+	fseek(fp, 0L, SEEK_END);
+	sz = ftell(fp);
+	fseek(fp, 0L, SEEK_SET);
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+
+	sz = fread(conn->msg_out, sizeof(char), sz, fp);
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+	rte_graph_cluster_stats_destroy(stats);
+
+	fclose(fp);
+}
+
+static void
+cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	graph_stats_print_to_file();
+}
+
+bool
+graph_status_get(void)
+{
+	return graph_started;
+}
+
+static void
+cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+		__rte_unused void *data)
+{
+	struct rte_node_ethdev_config *conf;
+	uint32_t nb_graphs = 0, nb_conf, i;
+	int rc = -EINVAL;
+
+	conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
+	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
+		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
+			if (graph_config.usecases[i].enabled) {
+				RTE_SET_USED(conf);
+				break;
+			}
+		}
+	}
+
+	if (!rc)
+		graph_started = true;
+}
+
+static int
+graph_config_add(char *usecases, struct graph_config *config)
+{
+	uint64_t lcore_id, core_num;
+	uint64_t eal_coremask = 0;
+
+	if (!parser_usecases_read(usecases))
+		return -EINVAL;
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		if (rte_lcore_is_enabled(lcore_id))
+			eal_coremask |= RTE_BIT64(lcore_id);
+	}
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		core_num = 1 << lcore_id;
+		if (config->params.coremask & core_num) {
+			if (eal_coremask & core_num)
+				continue;
+			else
+				return -EINVAL;
+		}
+	}
+
+	graph_config.params.bsz = config->params.bsz;
+	graph_config.params.tmo = config->params.tmo;
+	graph_config.params.coremask = config->params.coremask;
+	graph_config.model = config->model;
+	graph_config.pcap_ena = config->pcap_ena;
+	graph_config.num_pcap_pkts = config->num_pcap_pkts;
+	graph_config.pcap_file = strdup(config->pcap_file);
+
+	return 0;
+}
+
+void
+graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
+{
+
+	*pcap_ena = graph_config.pcap_ena;
+	*num_pkts = graph_config.num_pcap_pkts;
+	*file = graph_config.pcap_file;
+}
+
+int
+graph_walk_start(void *conf)
+{
+	struct lcore_conf *qconf;
+	struct rte_graph *graph;
+	uint32_t lcore_id;
+
+	RTE_SET_USED(conf);
+
+	lcore_id = rte_lcore_id();
+	qconf = &lcore_conf[lcore_id];
+	graph = qconf->graph;
+
+	if (!graph) {
+		RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
+		return 0;
+	}
+
+	RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
+		qconf->name, graph);
+
+	while (likely(!force_quit))
+		rte_graph_walk(graph);
+
+	return 0;
+}
+
+void
+graph_stats_print(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 = "worker_*";
+
+	/* 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);
+		if (app_graph_exit())
+			force_quit = true;
+	}
+
+	rte_graph_cluster_stats_destroy(stats);
+}
+
+uint64_t
+graph_coremask_get(void)
+{
+	return graph_config.params.coremask;
+}
+
+static void
+cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
+{
+	struct graph_config_cmd_tokens *res = parsed_result;
+	struct graph_config config;
+	char *model_name;
+	uint8_t model;
+	int rc;
+
+	model_name = res->model_name;
+	if (strcmp(model_name, "default") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "rtc") == 0) {
+		model = GRAPH_MODEL_RTC;
+	} else if (strcmp(model_name, "mcd") == 0) {
+		model = GRAPH_MODEL_MCD;
+	} else {
+		printf(MSG_ARG_NOT_FOUND, "model arguments");
+		return;
+	}
+
+	config.params.bsz = res->size;
+	config.params.tmo = res->ns;
+	config.params.coremask = res->mask;
+	config.model = model;
+	config.pcap_ena = res->pcap_ena;
+	config.num_pcap_pkts = res->num_pcap_pkts;
+	config.pcap_file = res->pcap_file;
+	rc = graph_config_add(res->usecase, &config);
+	if (rc < 0) {
+		cli_exit();
+		printf(MSG_CMD_FAIL, res->graph);
+		rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
+	}
+}
+
+static void
+cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
+	       __rte_unused void *data)
+{
+	size_t len;
+
+	len = strlen(conn->msg_out);
+	conn->msg_out += len;
+	snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n",
+		 "----------------------------- graph command help -----------------------------",
+		 cmd_graph_help, "graph start", "graph stats show");
+
+	len = strlen(conn->msg_out);
+	conn->msg_out_len_max -= len;
+}
+
+cmdline_parse_token_string_t graph_display_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_display_stats =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
+cmdline_parse_token_string_t graph_display_show =
+	TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
+
+cmdline_parse_inst_t graph_stats_cmd_ctx = {
+	.f = cli_graph_stats,
+	.data = NULL,
+	.help_str = "graph stats show",
+	.tokens = {
+		(void *)&graph_display_graph,
+		(void *)&graph_display_stats,
+		(void *)&graph_display_show,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_start_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_start =
+	TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
+
+cmdline_parse_inst_t graph_start_cmd_ctx = {
+	.f = cli_graph_start,
+	.data = NULL,
+	.help_str = "graph start",
+	.tokens = {
+		(void *)&graph_config_start_graph,
+		(void *)&graph_config_start,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_config_add_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
+cmdline_parse_token_string_t graph_config_add_usecase =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
+cmdline_parse_token_string_t graph_config_add_coremask =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
+cmdline_parse_token_num_t graph_config_add_mask =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_bsz =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
+cmdline_parse_token_num_t graph_config_add_size =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
+cmdline_parse_token_string_t graph_config_add_tmo =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
+cmdline_parse_token_num_t graph_config_add_ns =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_model =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
+cmdline_parse_token_string_t graph_config_add_model_name =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
+cmdline_parse_token_string_t graph_config_add_capt_ena =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
+cmdline_parse_token_num_t graph_config_add_pcap_ena =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
+cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
+cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
+	TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
+cmdline_parse_token_string_t graph_config_add_capt_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
+cmdline_parse_token_string_t graph_config_add_pcap_file =
+	TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
+
+cmdline_parse_inst_t graph_config_cmd_ctx = {
+	.f = cli_graph,
+	.data = NULL,
+	.help_str = cmd_graph_help,
+	.tokens = {
+		(void *)&graph_config_add_graph,
+		(void *)&graph_config_add_usecase,
+		(void *)&graph_config_add_coremask,
+		(void *)&graph_config_add_mask,
+		(void *)&graph_config_add_bsz,
+		(void *)&graph_config_add_size,
+		(void *)&graph_config_add_tmo,
+		(void *)&graph_config_add_ns,
+		(void *)&graph_config_add_model,
+		(void *)&graph_config_add_model_name,
+		(void *)&graph_config_add_capt_ena,
+		(void *)&graph_config_add_pcap_ena,
+		(void *)&graph_config_add_capt_pkts_count,
+		(void *)&graph_config_add_num_pcap_pkts,
+		(void *)&graph_config_add_capt_file,
+		(void *)&graph_config_add_pcap_file,
+		NULL,
+	},
+};
+
+cmdline_parse_token_string_t graph_help_cmd =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
+cmdline_parse_token_string_t graph_help_graph =
+	TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
+
+cmdline_parse_inst_t graph_help_cmd_ctx = {
+	.f = cli_graph_help,
+	.data = NULL,
+	.help_str = "",
+	.tokens = {
+		(void *)&graph_help_cmd,
+		(void *)&graph_help_graph,
+		NULL,
+	},
+};
diff --git a/app/graph/graph.h b/app/graph/graph.h
new file mode 100644
index 0000000000..a14fa37ccd
--- /dev/null
+++ b/app/graph/graph.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_H
+#define APP_GRAPH_H
+
+#include <cmdline_parse.h>
+
+extern cmdline_parse_inst_t graph_config_cmd_ctx;
+extern cmdline_parse_inst_t graph_start_cmd_ctx;
+extern cmdline_parse_inst_t graph_stats_cmd_ctx;
+extern cmdline_parse_inst_t graph_help_cmd_ctx;
+
+int graph_walk_start(void *conf);
+void graph_stats_print(void);
+void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
+uint64_t graph_coremask_get(void);
+bool graph_status_get(void);
+
+#endif
diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
new file mode 100644
index 0000000000..a48a35daa3
--- /dev/null
+++ b/app/graph/graph_priv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_PRIV_H
+#define APP_GRAPH_PRIV_H
+
+#define MAX_GRAPH_USECASES 32
+
+struct graph_help_cmd_tokens {
+	cmdline_fixed_string_t help;
+	cmdline_fixed_string_t graph;
+};
+
+struct graph_start_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t start;
+};
+
+struct graph_stats_cmd_tokens {
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t stats;
+};
+
+struct graph_config_cmd_tokens {
+	cmdline_fixed_string_t graph;
+	cmdline_fixed_string_t usecase;
+	cmdline_fixed_string_t bsz;
+	cmdline_fixed_string_t tmo;
+	cmdline_fixed_string_t coremask;
+	cmdline_fixed_string_t model;
+	cmdline_fixed_string_t capt_ena;
+	cmdline_fixed_string_t capt_pkts_count;
+	cmdline_fixed_string_t capt_file;
+	cmdline_fixed_string_t model_name;
+	cmdline_fixed_string_t pcap_file;
+	uint16_t size;
+	uint64_t ns;
+	uint64_t mask;
+	uint64_t num_pcap_pkts;
+	uint8_t pcap_ena;
+};
+
+enum graph_model {
+	GRAPH_MODEL_RTC = 0x01,
+	GRAPH_MODEL_MCD = 0x02,
+};
+
+struct usecases {
+	char name[32];
+	bool enabled;
+};
+
+struct usecase_params {
+	uint64_t coremask;
+	uint32_t bsz;
+	uint32_t tmo;
+};
+
+struct graph_config {
+	struct usecases usecases[MAX_GRAPH_USECASES];
+	struct usecase_params params;
+	enum graph_model model;
+	uint64_t num_pcap_pkts;
+	char *pcap_file;
+	uint8_t pcap_ena;
+};
+
+#endif
diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
index db3354c270..fc83586427 100644
--- a/app/graph/ip4_route.c
+++ b/app/graph/ip4_route.c
@@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
 	ipv4route->via = route->via;
 	ipv4route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route4_rewirte_table_update(ipv4route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route4, ipv4route, next);
 	return 0;
 free:
diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
index e793cde830..1fa4865220 100644
--- a/app/graph/ip6_route.c
+++ b/app/graph/ip6_route.c
@@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
 	}
 	ipv6route->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = route6_rewirte_table_update(ipv6route);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&route6, ipv6route, next);
 	return 0;
 free:
diff --git a/app/graph/meson.build b/app/graph/meson.build
index d8391d5cae..15d16a302e 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -14,6 +14,7 @@ sources = files(
         'conn.c',
         'ethdev_rx.c',
         'ethdev.c',
+        'graph.c',
         'ip4_route.c',
         'ip6_route.c',
         'main.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 56b7c94ecc..392dcfb222 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -12,6 +12,7 @@
 #include "conn.h"
 #include "ethdev.h"
 #include "ethdev_rx.h"
+#include "graph.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/app/graph/neigh.c b/app/graph/neigh.c
index 0cee502719..22be7361e3 100644
--- a/app/graph/neigh.c
+++ b/app/graph/neigh.c
@@ -154,11 +154,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
 	v4_config->mac = mac;
 	v4_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc = ip4_rewrite_node_add(v4_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
 	return 0;
 free:
@@ -187,11 +190,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
 	v6_config->mac = mac;
 	v6_config->is_used = true;
 
-	/* FIXME: Get graph status here and then update table */
+	if (!graph_status_get())
+		goto exit;
+
 	rc =  ip6_rewrite_node_add(v6_config);
 	if (rc)
 		goto free;
 
+exit:
 	TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
 	return 0;
 free:
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 318d92a0fb..08ec57b7f8 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -71,6 +71,25 @@ file to express the requested use case configuration.
    +--------------------------------------+-----------------------------------+---------+----------+
    |               Command                |             Description           | Dynamic | Optional |
    +======================================+===================================+=========+==========+
+   | | graph <usecases> [bsz <size>]      | | Command to express the desired  |   No    |    No    |
+   | | [tmo <ns>] [coremask <bitmask>]    | | use case. Also enables/disable  |         |          |
+   | | model <rtc/mcd/default> pcap_enable| | pcap capturing.                 |         |          |
+   | | <0/1> num_pcap_pkts <num> pcap_file|                                   |         |          |
+   | | <output_capture_file>              |                                   |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | graph start                          | | Command to start the graph.     |   No    |    No    |
+   |                                      | | This command triggers that no   |         |          |
+   |                                      | | more commands are left to be    |         |          |
+   |                                      | | parsed and graph initialization |         |          |
+   |                                      | | can be started now. It must be  |         |          |
+   |                                      | | the last command in usecase.cli |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | graph stats show                     | | Command to dump current graph   |   Yes   |    Yes   |
+   |                                      | | statistics.                     |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
+   | help graph                           | | Command to dump graph help      |   Yes   |    Yes   |
+   |                                      | | message.                        |         |          |
+   +--------------------------------------+-----------------------------------+---------+----------+
    | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
    | | <mbuf_size> buffers                | | will be further associated to   |         |          |
    | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 11/12] app/graph: support CLI option to enable graph stats
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (9 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 10/12] app/graph: support graph " skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23  7:06                                     ` Nithin Dabilpuram
  2023-10-19 17:30                                   ` [PATCH v11 12/12] app/graph: support l3fwd use case skori
                                                     ` (2 subsequent siblings)
  13 siblings, 1 reply; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Sunil Kumar Kori <skori@marvell.com>

Adds application's command line parameter "--enable-graph-stats"
to enable dumping graph stats on console.

By default, no graph stats will be printed on console but same can
be dumped via telnet session using "graph stats show" command.

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/main.c           | 17 ++++++++++++++++-
 app/graph/module_api.h     |  2 ++
 doc/guides/tools/graph.rst |  4 ++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/app/graph/main.c b/app/graph/main.c
index c1cb435588..465376425c 100644
--- a/app/graph/main.c
+++ b/app/graph/main.c
@@ -21,12 +21,13 @@
 volatile bool force_quit;
 struct conn *conn;
 
-static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
+static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
 			    "[--help]\n";
 
 static struct app_params {
 	struct conn_params conn;
 	char *script_name;
+	bool enable_graph_stats;
 } app = {
 	.conn = {
 		.welcome = "\nWelcome!\n\n",
@@ -40,6 +41,7 @@ static struct app_params {
 		.msg_handle_arg = NULL, /* set later. */
 	},
 	.script_name = NULL,
+	.enable_graph_stats = false,
 };
 
 static void
@@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
 {
 	struct option lgopts[] = {
 		{"help", 0, 0, 'H'},
+		{"enable-graph-stats", 0, 0, 'g'},
 	};
 	int h_present, p_present, s_present, n_args, i;
 	char *app_name = argv[0];
@@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
 			}
 			break;
 
+		case 'g':
+			app.enable_graph_stats = true;
+			printf("WARNING! Telnet session can not be accessed with"
+			       "--enable-graph-stats");
+			break;
+
 		case 'H':
 		default:
 			printf(usage, app_name);
@@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
 	return 0;
 }
 
+bool
+app_graph_stats_enabled(void)
+{
+	return app.enable_graph_stats;
+}
+
 bool
 app_graph_exit(void)
 {
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index 392dcfb222..a7d287f5c8 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -24,5 +24,7 @@
 extern volatile bool force_quit;
 extern struct conn *conn;
 
+bool app_graph_stats_enabled(void);
 bool app_graph_exit(void);
+
 #endif
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index 08ec57b7f8..bd8611a3d0 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -55,6 +55,10 @@ Following are the application command-line options:
         a mandatory parameter which will be used to create desired graph
         for a given use case.
 
+* ``--enable-graph-stats``
+
+       Enable graph statistics printing on console. By default graph statistics are disabled.
+
 * ``--help``
 
        Dumps application usage
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* [PATCH v11 12/12] app/graph: support l3fwd use case
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (10 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 11/12] app/graph: support CLI option to enable graph stats skori
@ 2023-10-19 17:30                                   ` skori
  2023-10-23 13:03                                   ` [PATCH v11 00/12] add CLI based graph application Sunil Kumar Kori
  2023-11-03 16:44                                   ` Thomas Monjalon
  13 siblings, 0 replies; 182+ messages in thread
From: skori @ 2023-10-19 17:30 UTC (permalink / raw)
  To: Sunil Kumar Kori, Rakesh Kudurumalla; +Cc: dev, Jerin Jacob

From: Rakesh Kudurumalla <rkudurumalla@marvell.com>

Adds an use case l3fwd. It contains a dedicated l3fwd.cli file
mentioning commands to configure the required resources.

Once application successfully parses the l3fwd.cli then a graph is
created having below nodes:
 - ethdev_rx -> pkt_cls

 - pkt_cls -> ip4_lookup
 - pkt_cls -> ip6_lookup
 - pkt_cls -> pkt_drop

 - ip4_lookup -> ip4_rewrite
 - ip4_lookup -> pkt_drop

 - ip6_lookup -> ip6_rewrite
 - ip6_lookup -> pkt_drop

 - ip4_rewrite -> ethdev_tx
 - ip4_rewrite -> pkt_drop

 - ip6_rewrite -> ethdev_tx
 - ip6_rewrite -> pkt_drop

 - ethdev_tx -> pkt_drop

Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
---
 app/graph/examples/l3fwd.cli                 |  73 +++++++
 app/graph/examples/l3fwd_pcap.cli            |  71 +++++++
 app/graph/graph.c                            |   2 +-
 app/graph/l3fwd.c                            | 136 ++++++++++++
 app/graph/l3fwd.h                            |  11 +
 app/graph/meson.build                        |   1 +
 app/graph/module_api.h                       |   1 +
 doc/guides/tools/graph.rst                   |  84 ++++++++
 doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++++++++++++++++
 9 files changed, 588 insertions(+), 1 deletion(-)
 create mode 100644 app/graph/examples/l3fwd.cli
 create mode 100644 app/graph/examples/l3fwd_pcap.cli
 create mode 100644 app/graph/l3fwd.c
 create mode 100644 app/graph/l3fwd.h
 create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg

diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
new file mode 100644
index 0000000000..c4977d4322
--- /dev/null
+++ b/app/graph/examples/l3fwd.cli
@@ -0,0 +1,73 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0xff bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev 0002:02:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:03:00.0 rxq 1 txq 8 mempool0
+ethdev 0002:02:00.0 mtu 1700
+ethdev 0002:03:00.0 mtu 1700
+ethdev 0002:02:00.0 promiscuous on
+ethdev 0002:03:00.0 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev 0002:02:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev 0002:03:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev 0002:02:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev 0002:03:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port 0002:02:00.0 queue 0 core 1
+ethdev_rx map port 0002:03:00.0 queue 0 core 2
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/examples/l3fwd_pcap.cli b/app/graph/examples/l3fwd_pcap.cli
new file mode 100644
index 0000000000..30dde74a65
--- /dev/null
+++ b/app/graph/examples/l3fwd_pcap.cli
@@ -0,0 +1,71 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2023 Marvell.
+
+;
+; Graph configuration for given usecase
+;
+graph l3fwd coremask 0x03 bsz 32 tmo 10 model default pcap_enable 1 num_pcap_pkts 100000 pcap_file /tmp/output.pcap
+
+;
+; Mempools to be attached with ethdev
+;
+mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
+
+;
+; DPDK devices and configuration.
+;
+; Note: Customize the parameters below to match your setup.
+;
+ethdev net_pcap0 rxq 1 txq 1 mempool0
+ethdev net_pcap1 rxq 1 txq 1 mempool0
+ethdev net_pcap0 promiscuous on
+ethdev net_pcap1 promiscuous on
+
+;
+; IPv4 addresses assigned to DPDK devices
+;
+ethdev net_pcap0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
+ethdev net_pcap1 ip4 addr add 20.0.2.1 netmask 255.255.255.0
+
+;
+; IPv6 addresses assigned to DPDK devices
+;
+ethdev net_pcap0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+ethdev net_pcap1 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
+
+;
+; IPv4 routes which are installed to ipv4_lookup node for LPM processing
+;
+ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
+ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
+
+;
+; IPv6 routes which are installed to ipv6_lookup node for LPM processing
+;
+ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
+ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
+
+;
+; Peer MAC and IPv4 address mapping
+;
+neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
+neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
+
+;
+; Peer MAC and IPv6 address mapping
+;
+neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
+neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
+
+;
+; Port-Queue-Core mapping for ethdev_rx node
+;
+ethdev_rx map port net_pcap0 queue 0 core 1
+ethdev_rx map port net_pcap1 queue 0 core 1
+
+;
+; Graph start command to create graph.
+;
+; Note: No more command should come after this.
+;
+graph start
diff --git a/app/graph/graph.c b/app/graph/graph.c
index 74a99dd68e..a65723a196 100644
--- a/app/graph/graph.c
+++ b/app/graph/graph.c
@@ -269,7 +269,7 @@ cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *c
 	for (i = 0; i < MAX_GRAPH_USECASES; i++) {
 		if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
 			if (graph_config.usecases[i].enabled) {
-				RTE_SET_USED(conf);
+				rc  = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
 				break;
 			}
 		}
diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
new file mode 100644
index 0000000000..a2648df27d
--- /dev/null
+++ b/app/graph/l3fwd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_lcore.h>
+#include <rte_node_eth_api.h>
+
+#include "module_api.h"
+
+static int
+l3fwd_pattern_configure(void)
+{
+	/* Graph initialization. 8< */
+	static const char * const default_patterns[] = {
+		"ip4*",
+		"ethdev_tx-*",
+		"pkt_drop",
+	};
+	struct rte_graph_param graph_conf;
+	const char **node_patterns;
+	uint64_t pcap_pkts_count;
+	struct lcore_conf *qconf;
+	uint16_t nb_patterns;
+	uint8_t pcap_ena;
+	int rc, lcore_id;
+	char *pcap_file;
+
+	nb_patterns = RTE_DIM(default_patterns);
+	node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
+			sizeof(*node_patterns));
+	if (!node_patterns)
+		return -ENOMEM;
+	memcpy(node_patterns, default_patterns,
+			nb_patterns * sizeof(*node_patterns));
+
+	memset(&graph_conf, 0, sizeof(graph_conf));
+	graph_conf.node_patterns = node_patterns;
+
+	/* Pcap config */
+	graph_pcap_config_get(&pcap_ena, &pcap_pkts_count, &pcap_file);
+	graph_conf.pcap_enable = pcap_ena;
+	graph_conf.num_pkt_to_capture = pcap_pkts_count;
+	graph_conf.pcap_filename = strdup(pcap_file);
+
+	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
+		rte_graph_t graph_id;
+		rte_edge_t i;
+
+		if (rte_lcore_is_enabled(lcore_id) == 0)
+			continue;
+
+		qconf = &lcore_conf[lcore_id];
+
+		/* Skip graph creation if no source exists */
+		if (!qconf->n_rx_queue)
+			continue;
+
+		/* Add rx node patterns of this lcore */
+		for (i = 0; i < qconf->n_rx_queue; i++) {
+			graph_conf.node_patterns[nb_patterns + i] =
+				qconf->rx_queue_list[i].node_name;
+		}
+
+		graph_conf.nb_node_patterns = nb_patterns + i;
+		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
+
+		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
+				lcore_id);
+
+		graph_id = rte_graph_create(qconf->name, &graph_conf);
+		if (graph_id == RTE_GRAPH_ID_INVALID)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_create(): graph_id invalid"
+					" for lcore %u\n", lcore_id);
+
+		qconf->graph_id = graph_id;
+		qconf->graph = rte_graph_lookup(qconf->name);
+		/* >8 End of graph initialization. */
+		if (!qconf->graph)
+			rte_exit(EXIT_FAILURE,
+					"rte_graph_lookup(): graph %s not found\n",
+					qconf->name);
+	}
+
+	rc = route_ip4_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
+
+	rc = route_ip6_add_to_lookup();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
+
+	rc = neigh_ip4_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
+
+	rc = neigh_ip6_add_to_rewrite();
+	if (rc < 0)
+		rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
+
+	/* Launch per-lcore init on every worker lcore */
+	rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
+
+	/* Accumulate and print stats on main until exit */
+	if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
+		graph_stats_print();
+
+	return rc;
+}
+
+int
+usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs, uint16_t nb_graphs)
+{
+	int rc;
+
+	rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
+	if (rc)
+		rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
+
+	rc = l3fwd_pattern_configure();
+	if (rc)
+		rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
+
+	return rc;
+}
diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
new file mode 100644
index 0000000000..e1d23165e6
--- /dev/null
+++ b/app/graph/l3fwd.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Marvell.
+ */
+
+#ifndef APP_GRAPH_L3FWD_H
+#define APP_GRAPH_L3FWD_H
+
+int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
+			    uint16_t nb_graphs);
+
+#endif
diff --git a/app/graph/meson.build b/app/graph/meson.build
index 15d16a302e..5b0f966d99 100644
--- a/app/graph/meson.build
+++ b/app/graph/meson.build
@@ -17,6 +17,7 @@ sources = files(
         'graph.c',
         'ip4_route.c',
         'ip6_route.c',
+        'l3fwd.c',
         'main.c',
         'mempool.c',
         'neigh.c',
diff --git a/app/graph/module_api.h b/app/graph/module_api.h
index a7d287f5c8..7193e0b616 100644
--- a/app/graph/module_api.h
+++ b/app/graph/module_api.h
@@ -13,6 +13,7 @@
 #include "ethdev.h"
 #include "ethdev_rx.h"
 #include "graph.h"
+#include "l3fwd.h"
 #include "mempool.h"
 #include "neigh.h"
 #include "route.h"
diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
index bd8611a3d0..2c04531552 100644
--- a/doc/guides/tools/graph.rst
+++ b/doc/guides/tools/graph.rst
@@ -63,6 +63,83 @@ Following are the application command-line options:
 
        Dumps application usage
 
+Supported Use cases
+-------------------
+ * l3fwd
+
+l3fwd
+~~~~~
+
+This use case is supported for both H/W and PCAP vdev network devices. To demonstrate,
+corresponding .cli files are available at ``<dpdk_root_dir/app/graph/examples/>``
+named as ``l3fwd.cli`` and  ``l3fwd_pcap.cli`` respectively.
+
+Example Commands
+^^^^^^^^^^^^^^^^
+For H/W devices
+
+.. code-block:: console
+
+   ./dpdk-graph -c 0xff -a 0002:02:00.0 -a 0002:03:00.0 --
+             -s <dpdk_root_dir>/app/graph/examples/l3fwd.cli
+
+For net_pcapX devices
+
+.. code-block:: console
+
+   ./dpdk-graph -c 0xff --vdev=net_pcap0,rx_pcap=in_net_pcap0.pcap,tx_pcap=out_net_pcap1.pcap
+                        --vdev=net_pcap1,rx_pcap=in_net_pcap1.pcap,tx_pcap=out_net_pcap0.pcap
+                        -- -s <dpdk_root_dir>/app/graph/examples/l3fwd_pcap.cli
+
+Verifying traffic
+^^^^^^^^^^^^^^^^^
+
+``l3fwd.cli`` and ``l3fwd_pcap.cli`` creates setup with two network ports. Routing between
+these ports are done by lookup node routing information. For current use case, following
+routing table is used:
+
+.. code-block:: console
+
+   DIP        port
+   10.0.2.2    1
+   20.0.2.2    0
+
+On the successful execution of ``l3fwd.cli`` or ``l3fwd_pcap.cli``, user needs to send traffic
+with mentioned DIP.
+
+For net_pcapX devices, required pcap file should be created and passed to application. These
+pcap files can be created in several ways. Scapy is one of the method to get the same:
+
+.. code-block:: console
+
+   # scapy
+   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2")]
+   >>>
+   >>> wrpcap("in_net_pcap1.pcap",pkts)
+   >>>
+   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
+             Ether(dst="FA:09:F9:D7:E0:9D", src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="20.0.2.2")]
+   >>>
+   >>> wrpcap("in_net_pcap0.pcap",pkts)
+   >>> quit
+
 Supported CLI commands
 ----------------------
 
@@ -228,3 +305,10 @@ Created graph for use case
 
 On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
 This section mentions the created graph for each use case.
+
+l3fwd
+~~~~~
+
+.. _figure_l3fwd_graph:
+
+.. figure:: img/graph-usecase-l3fwd.*
diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-usecase-l3fwd.svg
new file mode 100644
index 0000000000..3b991c4cf0
--- /dev/null
+++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
+<!-- Copyright(C) 2023 Marvell. -->
+<!--
+
+Generated with following command
+dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
+
+cat dot.dot
+digraph dpdk_app_graph_l3fwd_nodes_flow {
+    ingress_port [shape=rect]
+    ethdev_rx
+    pkt_cls
+    ip4_lookup
+    ip6_lookup
+    ip4_rewrite
+    ip6_rewrite
+    ethdev_tx
+    pkt_drop
+    egress_port  [shape=rect]
+
+    ingress_port -> ethdev_rx [label="ingress packet"]
+
+    ethdev_rx -> pkt_cls
+
+    pkt_cls -> ip4_lookup [color="green"]
+    pkt_cls -> ip6_lookup [color="blue"]
+    pkt_cls -> pkt_drop   [color="red" style="dashed"]
+
+    ip4_lookup -> ip4_rewrite [color="green"]
+    ip4_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip6_lookup -> ip6_rewrite [color="blue"]
+    ip6_lookup -> pkt_drop [color="red" style="dashed"]
+
+    ip4_rewrite -> ethdev_tx [color="green"]
+    ip4_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ip6_rewrite -> ethdev_tx [color="blue"]
+    ip6_rewrite -> pkt_drop  [color="red" style="dashed"]
+
+    ethdev_tx -> egress_port [label="egress packet"]
+    ethdev_tx -> pkt_drop [color="red" style="dashed"]
+}
+
+ -->
+<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
+<svg width="550pt" height="510pt"
+ viewBox="0.00 0.00 549.50 510.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
+<title>dpdk_app_graph_l3fwd_nodes_flow</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
+<!-- ingress_port -->
+<g id="node1" class="node">
+<title>ingress_port</title>
+<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466 489.5,-502"/>
+<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-size="14.00">ingress_port</text>
+</g>
+<!-- ethdev_rx -->
+<g id="node2" class="node">
+<title>ethdev_rx</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-size="14.00">ethdev_rx</text>
+</g>
+<!-- ingress_port&#45;&gt;ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port&#45;&gt;ethdev_rx</title>
+<path fill="none" stroke="black" d="M436.5,-465.8C436.5,-454.16 436.5,-438.55 436.5,-425.24"/>
+<polygon fill="black" stroke="black" points="440,-425.18 436.5,-415.18 433,-425.18 440,-425.18"/>
+<text text-anchor="middle" x="489" y="-436.8" font-family="Times,serif" font-size="14.00">ingress packet</text>
+</g>
+<!-- pkt_cls -->
+<g id="node3" class="node">
+<title>pkt_cls</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-324" rx="42.79" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-320.3" font-family="Times,serif" font-size="14.00">pkt_cls</text>
+</g>
+<!-- ethdev_rx&#45;&gt;pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx&#45;&gt;pkt_cls</title>
+<path fill="none" stroke="black" d="M436.5,-378.81C436.5,-370.79 436.5,-361.05 436.5,-352.07"/>
+<polygon fill="black" stroke="black" points="440,-352.03 436.5,-342.03 433,-352.03 440,-352.03"/>
+</g>
+<!-- ip4_lookup -->
+<g id="node4" class="node">
+<title>ip4_lookup</title>
+<ellipse fill="none" stroke="black" cx="436.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="436.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip4_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls&#45;&gt;ip4_lookup</title>
+<path fill="none" stroke="green" d="M436.5,-305.81C436.5,-297.79 436.5,-288.05 436.5,-279.07"/>
+<polygon fill="green" stroke="green" points="440,-279.03 436.5,-269.03 433,-279.03 440,-279.03"/>
+</g>
+<!-- ip6_lookup -->
+<g id="node5" class="node">
+<title>ip6_lookup</title>
+<ellipse fill="none" stroke="black" cx="297.5" cy="-251" rx="60.39" ry="18"/>
+<text text-anchor="middle" x="297.5" y="-247.3" font-family="Times,serif" font-size="14.00">ip6_lookup</text>
+</g>
+<!-- pkt_cls&#45;&gt;ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls&#45;&gt;ip6_lookup</title>
+<path fill="none" stroke="blue" d="M410.36,-309.65C389.39,-298.94 359.66,-283.75 335.97,-271.65"/>
+<polygon fill="blue" stroke="blue" points="337.39,-268.45 326.9,-267.02 334.21,-274.68 337.39,-268.45"/>
+</g>
+<!-- pkt_drop -->
+<g id="node9" class="node">
+<title>pkt_drop</title>
+<ellipse fill="none" stroke="black" cx="361.5" cy="-18" rx="51.99" ry="18"/>
+<text text-anchor="middle" x="361.5" y="-14.3" font-family="Times,serif" font-size="14.00">pkt_drop</text>
+</g>
+<!-- pkt_cls&#45;&gt;pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M468.77,-311.93C493.88,-301.02 524.5,-281.64 524.5,-252 524.5,-252 524.5,-252 524.5,-104 524.5,-55.68 467.5,-34.79 420.91,-25.78"/>
+<polygon fill="red" stroke="red" points="421.31,-22.29 410.85,-23.98 420.08,-29.18 421.31,-22.29"/>
+</g>
+<!-- ip4_rewrite -->
+<g id="node6" class="node">
+<title>ip4_rewrite</title>
+<ellipse fill="none" stroke="black" cx="394.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="394.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip4_rewrite</text>
+</g>
+<!-- ip4_lookup&#45;&gt;ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup&#45;&gt;ip4_rewrite</title>
+<path fill="none" stroke="green" d="M426.55,-233.17C421.55,-224.72 415.38,-214.29 409.79,-204.85"/>
+<polygon fill="green" stroke="green" points="412.78,-203.02 404.67,-196.2 406.75,-206.59 412.78,-203.02"/>
+</g>
+<!-- ip4_lookup&#45;&gt;pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M449.33,-233.03C456.19,-222.87 463.94,-209.37 467.5,-196 471.62,-180.54 472.57,-175.18 467.5,-160 451.61,-112.41 412.64,-67.99 386.65,-42.17"/>
+<polygon fill="red" stroke="red" points="388.97,-39.54 379.36,-35.08 384.09,-44.56 388.97,-39.54"/>
+</g>
+<!-- ip6_rewrite -->
+<g id="node7" class="node">
+<title>ip6_rewrite</title>
+<ellipse fill="none" stroke="black" cx="210.5" cy="-178" rx="63.89" ry="18"/>
+<text text-anchor="middle" x="210.5" y="-174.3" font-family="Times,serif" font-size="14.00">ip6_rewrite</text>
+</g>
+<!-- ip6_lookup&#45;&gt;ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup&#45;&gt;ip6_rewrite</title>
+<path fill="none" stroke="blue" d="M277.76,-233.89C266.16,-224.42 251.31,-212.31 238.52,-201.87"/>
+<polygon fill="blue" stroke="blue" points="240.43,-198.9 230.46,-195.29 236,-204.33 240.43,-198.9"/>
+</g>
+<!-- ip6_lookup&#45;&gt;pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M302.02,-232.72C306.79,-214.59 314.55,-185.26 321.5,-160 332.39,-120.41 345.45,-74.7 353.61,-46.32"/>
+<polygon fill="red" stroke="red" points="357.08,-46.92 356.49,-36.34 350.35,-44.98 357.08,-46.92"/>
+</g>
+<!-- ethdev_tx -->
+<g id="node8" class="node">
+<title>ethdev_tx</title>
+<ellipse fill="none" stroke="black" cx="249.5" cy="-105" rx="55.79" ry="18"/>
+<text text-anchor="middle" x="249.5" y="-101.3" font-family="Times,serif" font-size="14.00">ethdev_tx</text>
+</g>
+<!-- ip4_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="green" d="M364.1,-162.12C341.96,-151.27 311.81,-136.51 287.98,-124.84"/>
+<polygon fill="green" stroke="green" points="289.39,-121.63 278.87,-120.38 286.31,-127.92 289.39,-121.63"/>
+</g>
+<!-- ip4_rewrite&#45;&gt;pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M390.91,-159.79C385.2,-132.48 374.03,-78.99 367.22,-46.38"/>
+<polygon fill="red" stroke="red" points="370.56,-45.26 365.09,-36.19 363.71,-46.69 370.56,-45.26"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite&#45;&gt;ethdev_tx</title>
+<path fill="none" stroke="blue" d="M219.74,-160.17C224.34,-151.81 230,-141.51 235.14,-132.14"/>
+<polygon fill="blue" stroke="blue" points="238.31,-133.65 240.05,-123.2 232.17,-130.28 238.31,-133.65"/>
+</g>
+<!-- ip6_rewrite&#45;&gt;pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M197.68,-160.05C184.87,-140.87 169.12,-109.39 184.5,-87 210.62,-48.98 261.18,-32.21 301.59,-24.82"/>
+<polygon fill="red" stroke="red" points="302.35,-28.24 311.63,-23.13 301.19,-21.33 302.35,-28.24"/>
+</g>
+<!-- ethdev_tx&#45;&gt;pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx&#45;&gt;pkt_drop</title>
+<path fill="none" stroke="red" stroke-dasharray="5,2" d="M270.3,-88.21C287.91,-74.85 313.31,-55.57 332.84,-40.75"/>
+<polygon fill="red" stroke="red" points="334.96,-43.54 340.81,-34.7 330.73,-37.96 334.96,-43.54"/>
+</g>
+<!-- egress_port -->
+<g id="node10" class="node">
+<title>egress_port</title>
+<polygon fill="none" stroke="black" points="101,-36 0,-36 0,0 101,0 101,-36"/>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">egress_port</text>
+</g>
+<!-- ethdev_tx&#45;&gt;egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx&#45;&gt;egress_port</title>
+<path fill="none" stroke="black" d="M217.08,-90.15C185.34,-76.59 136.54,-55.75 99.95,-40.12"/>
+<polygon fill="black" stroke="black" points="101.03,-36.78 90.45,-36.07 98.28,-43.21 101.03,-36.78"/>
+<text text-anchor="middle" x="211.5" y="-57.8" font-family="Times,serif" font-size="14.00">egress packet</text>
+</g>
+</g>
+</svg>
-- 
2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 01/12] app/graph: support application CLI framework
  2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
@ 2023-10-23  7:03                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:03 UTC (permalink / raw)
  To: skori; +Cc: Thomas Monjalon, Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 9:37 AM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds base framework to read a given .cli file as a command line
> parameter "-s".
>
> Example:
>  # ./dpdk-graph -c 0xff -- -s ./app/graph/examples/dummy.cli
>
> Each .cli file will contain commands to configure different module like
> mempool, ethdev, lookup tables, graph etc. Command parsing is backed by
> commandline library.
>
> Each module needs to expose its supported commands & corresponding
> callback functions to commandline library to get them parsed.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
> v10..v11
>  - Add information to kill telnet session in user guide.
>  - Merge l3fwd related information in a single section in user guide.
>  - Fix spellings.
>
> v9..v10
>  - Add l3fwd_pcap.cli for pcap devices.
>  - Update table generation mechanism user guide document
>
> v8..v9:
>  - Replace strcpy() to rte_strscpy()
>  - Update release note.
>  - Update user guide
>
> v7..v8:
>  - Fix klocwork issues.
>
> v6..v7:
>  - Fix FreeBSD build error.
>  - Make route and neigh runtime configuration too.
>
> v5..v6:
>  - Fix build errors.
>  - Fix checkpatch errors.
>  - Fix individual patch build errors.
>
> v4..v5:
>  - Fix application exit issue.
>  - Enable graph packet capture feature.
>  - Fix graph coremask synchronization with eal coremask.
>  - Update user guide.
>
> https://patches.dpdk.org/project/dpdk/patch/20230919160455.1678716-1-skori@marvell.com/
>
> v3..v4:
>  - Use commandline library to parse command tokens.
>  - Split to multiple smaller patches.
>  - Make neigh and route as dynamic database.
>  - add ethdev and graph stats command via telnet.
>  - Update user guide.
>
> https://patches.dpdk.org/project/dpdk/patch/20230908104907.4060511-1-skori@marvell.com/
>
>  MAINTAINERS                            |   7 ++
>  app/graph/cli.c                        | 115 ++++++++++++++++++++++
>  app/graph/cli.h                        |  32 +++++++
>  app/graph/main.c                       | 128 +++++++++++++++++++++++++
>  app/graph/meson.build                  |  15 +++
>  app/graph/module_api.h                 |  16 ++++
>  app/meson.build                        |   1 +
>  doc/guides/rel_notes/release_23_11.rst |   7 ++
>  doc/guides/tools/graph.rst             |  75 +++++++++++++++
>  doc/guides/tools/index.rst             |   1 +
>  10 files changed, 397 insertions(+)
>  create mode 100644 app/graph/cli.c
>  create mode 100644 app/graph/cli.h
>  create mode 100644 app/graph/main.c
>  create mode 100644 app/graph/meson.build
>  create mode 100644 app/graph/module_api.h
>  create mode 100644 doc/guides/tools/graph.rst
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4083658697..88a71d5455 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1818,6 +1818,13 @@ F: dts/
>  F: devtools/dts-check-format.sh
>  F: doc/guides/tools/dts.rst
>
> +Graph application
> +M: Sunil Kumar Kori <skori@marvell.com>
> +M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> +F: app/graph/
> +F: doc/guides/tools/graph.rst
> +F: doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
>
>  Other Example Applications
>  --------------------------
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> new file mode 100644
> index 0000000000..df4f8fcbb8
> --- /dev/null
> +++ b/app/graph/cli.c
> @@ -0,0 +1,115 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_common.h>
> +
> +#include "module_api.h"
> +
> +#define CMD_MAX_TOKENS 256
> +#define MAX_LINE_SIZE 2048
> +
> +cmdline_parse_ctx_t modules_ctx[] = {
> +       NULL,
> +};
> +
> +static struct cmdline *cl;
> +
> +static int
> +is_comment(char *in)
> +{
> +       if ((strlen(in) && index("!#%;", in[0])) ||
> +               (strncmp(in, "//", 2) == 0) ||
> +               (strncmp(in, "--", 2) == 0))
> +               return 1;
> +
> +       return 0;
> +}
> +
> +void
> +cli_init(void)
> +{
> +       cl = cmdline_stdin_new(modules_ctx, "");
> +}
> +
> +void
> +cli_exit(void)
> +{
> +       cmdline_stdin_exit(cl);
> +}
> +
> +void
> +cli_process(char *in, char *out, size_t out_size, __rte_unused void *obj)
> +{
> +       int rc;
> +
> +       if (is_comment(in))
> +               return;
> +
> +       rc = cmdline_parse(cl, in);
> +       if (rc == CMDLINE_PARSE_AMBIGUOUS)
> +               snprintf(out, out_size, MSG_CMD_FAIL, "Ambiguous command");
> +       else if (rc == CMDLINE_PARSE_NOMATCH)
> +               snprintf(out, out_size, MSG_CMD_FAIL, "Command mismatch");
> +       else if (rc == CMDLINE_PARSE_BAD_ARGS)
> +               snprintf(out, out_size, MSG_CMD_FAIL, "Bad arguments");
> +
> +       return;
> +
> +}
> +
> +int
> +cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max, void *obj)
> +{
> +       char *msg_in = NULL, *msg_out = NULL;
> +       int rc = -EINVAL;
> +       FILE *f = NULL;
> +
> +       /* Check input arguments */
> +       if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
> +           (msg_out_len_max == 0))
> +               return rc;
> +
> +       msg_in = malloc(msg_in_len_max + 1);
> +       msg_out = malloc(msg_out_len_max + 1);
> +       if ((msg_in == NULL) || (msg_out == NULL)) {
> +               rc = -ENOMEM;
> +               goto exit;
> +       }
> +
> +       /* Open input file */
> +       f = fopen(file_name, "r");
> +       if (f == NULL) {
> +               rc = -EIO;
> +               goto exit;
> +       }
> +
> +       /* Read file */
> +       while (fgets(msg_in, msg_in_len_max, f) != NULL) {
> +               msg_out[0] = 0;
> +
> +               cli_process(msg_in, msg_out, msg_out_len_max, obj);
> +
> +               if (strlen(msg_out))
> +                       printf("%s", msg_out);
> +       }
> +
> +       /* Close file */
> +       fclose(f);
> +       rc = 0;
> +
> +exit:
> +       free(msg_out);
> +       free(msg_in);
> +       return rc;
> +}
> diff --git a/app/graph/cli.h b/app/graph/cli.h
> new file mode 100644
> index 0000000000..652f948352
> --- /dev/null
> +++ b/app/graph/cli.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_H
> +#define APP_GRAPH_CLI_H
> +
> +/* Macros */
> +#define MSG_OUT_OF_MEMORY   "Not enough memory.\n"
> +#define MSG_CMD_UNKNOWN     "Unknown command \"%s\".\n"
> +#define MSG_CMD_UNIMPLEM    "Command \"%s\" not implemented.\n"
> +#define MSG_ARG_NOT_ENOUGH  "Not enough arguments for command \"%s\".\n"
> +#define MSG_ARG_TOO_MANY    "Too many arguments for command \"%s\".\n"
> +#define MSG_ARG_MISMATCH    "Wrong number of arguments for command \"%s\".\n"
> +#define MSG_ARG_NOT_FOUND   "Argument \"%s\" not found.\n"
> +#define MSG_ARG_INVALID     "Invalid value for argument \"%s\".\n"
> +#define MSG_FILE_ERR        "Error in file \"%s\" at line %u.\n"
> +#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
> +#define MSG_CMD_FAIL        "Command \"%s\" failed.\n"
> +
> +#define APP_CLI_CMD_NAME_SIZE  64
> +
> +void cli_init(void);
> +
> +void cli_exit(void);
> +
> +void cli_process(char *in, char *out, size_t out_size, void *arg);
> +
> +int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t msg_out_len_max,
> +                      void *arg);
> +
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> new file mode 100644
> index 0000000000..734a94444e
> --- /dev/null
> +++ b/app/graph/main.c
> @@ -0,0 +1,128 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/select.h>
> +#include <unistd.h>
> +
> +#include <rte_eal.h>
> +#include <rte_launch.h>
> +
> +#include "module_api.h"
> +
> +volatile bool force_quit;
> +
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
> +                           "[--help]\n";
> +
> +static struct app_params {
> +       char *script_name;
> +} app = {
> +       .script_name = NULL,
> +};
> +
> +static void
> +signal_handler(int signum)
> +{
> +       if (signum == SIGINT || signum == SIGTERM) {
> +               printf("\n\nSignal %d received, preparing to exit...\n", signum);
> +               force_quit = true;
> +       }
> +}
> +
> +static int
> +app_args_parse(int argc, char **argv)
> +{
> +       struct option lgopts[] = {
> +               {"help", 0, 0, 'H'},
> +       };
> +       int s_present, n_args, i;
> +       char *app_name = argv[0];
> +       int opt, option_index;
> +
> +       /* Skip EAL input args */
> +       n_args = argc;
> +       for (i = 0; i < n_args; i++)
> +               if (strcmp(argv[i], "--") == 0) {
> +                       argc -= i;
> +                       argv += i;
> +                       break;
> +               }
> +
> +       if (i == n_args)
> +               return 0;
> +
> +       /* Parse args */
> +       s_present = 0;
> +
> +       while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
> +               switch (opt) {
> +               case 's':
> +                       if (s_present) {
> +                               printf("Error: Multiple -s arguments\n");
> +                               return -1;
> +                       }
> +                       s_present = 1;
> +
> +                       if (!strlen(optarg)) {
> +                               printf("Error: Argument for -s not provided\n");
> +                               return -1;
> +                       }
> +
> +                       app.script_name = strdup(optarg);
> +                       if (app.script_name == NULL) {
> +                               printf("Error: Not enough memory\n");
> +                               return -1;
> +                       }
> +                       break;
> +
> +               case 'H':
> +               default:
> +                       printf(usage, app_name);
> +                       return -1;
> +               }
> +       }
> +       optind = 1; /* reset getopt lib */
> +
> +       return 0;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +       int rc;
> +
> +       /* Parse application arguments */
> +       rc = app_args_parse(argc, argv);
> +       if (rc < 0)
> +               return rc;
> +
> +       /* EAL */
> +       rc = rte_eal_init(argc, argv);
> +       if (rc < 0) {
> +               printf("Error: EAL initialization failed (%d)\n", rc);
> +               return rc;
> +       };
> +
> +       force_quit = false;
> +       signal(SIGINT, signal_handler);
> +       signal(SIGTERM, signal_handler);
> +
> +       cli_init();
> +
> +       /* Script */
> +       if (app.script_name) {
> +               cli_script_process(app.script_name, 0,
> +                       0, NULL);
> +       }
> +
> +       cli_exit();
> +       rte_eal_cleanup();
> +       return 0;
> +}
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> new file mode 100644
> index 0000000000..ed33a04476
> --- /dev/null
> +++ b/app/graph/meson.build
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 Marvell.
> +
> +# override default name to drop the hyphen
> +name = 'graph'
> +build = cc.has_header('sys/epoll.h')
> +if not build
> +    subdir_done()
> +endif
> +
> +deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
> +sources = files(
> +        'cli.c',
> +        'main.c',
> +)
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> new file mode 100644
> index 0000000000..372aeae7e3
> --- /dev/null
> +++ b/app/graph/module_api.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MODULE_API_H
> +#define APP_GRAPH_MODULE_API_H
> +
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include "cli.h"
> +/*
> + * Externs
> + */
> +extern volatile bool force_quit;
> +
> +#endif
> diff --git a/app/meson.build b/app/meson.build
> index e4bf5c531c..728c936383 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -17,6 +17,7 @@ endif
>  apps = [
>          'dumpcap',
>          'pdump',
> +        'graph',
>          'proc-info',
>          'test-acl',
>          'test-bbdev',
> diff --git a/doc/guides/rel_notes/release_23_11.rst b/doc/guides/rel_notes/release_23_11.rst
> index 0a6fc76a9d..f43b416565 100644
> --- a/doc/guides/rel_notes/release_23_11.rst
> +++ b/doc/guides/rel_notes/release_23_11.rst
> @@ -243,6 +243,13 @@ New Features
>    Added dispatcher library which purpose is to help decouple different
>    parts (modules) of an eventdev-based application.
>
> +* **Added CLI based graph application.**
> +
> +  Added CLI based graph application which exercises on different usecases.
> +  Application provides a framework so that each usecase can be added via CLI
> +  file. Each CLI will further be translated into a graph representing user's
> +  required usecase.
> +
>
>  Removed Items
>  -------------
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> new file mode 100644
> index 0000000000..cb005e7856
> --- /dev/null
> +++ b/doc/guides/tools/graph.rst
> @@ -0,0 +1,75 @@
> +..  SPDX-License-Identifier: BSD-3-Clause
> +    Copyright(c) 2023 Marvell.
> +
> +dpdk-graph Application
> +======================
> +
> +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> +application that allows exercising various graph use cases.
> +This application has a generic framework to add new graph based use cases to
> +verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
> +Based on the input file, application creates a graph to cater the use case.
> +
> +Also this application framework can be used by other graph based applications.
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options which can be provided in
> +following syntax
> +
> +.. code-block:: console
> +
> +   dpdk-graph [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +Following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> +        Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
> +        list of cores to be used.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +Following are the application command-line options:
> +
> +* ``-s``
> +
> +        Script name with absolute path which specifies the use case. It is
> +        a mandatory parameter which will be used to create desired graph
> +        for a given use case.
> +
> +* ``--help``
> +
> +       Dumps application usage
> +
> +Supported CLI commands
> +----------------------
> +
> +This section provides details on commands which can be used in ``<usecase>.cli``
> +file to express the requested use case configuration.
> +
> +.. table:: Exposed CLIs
> +   :widths: auto
> +
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   |               Command                |             Description           | Dynamic | Optional |
> +   +======================================+===================================+=========+==========+
> +   |                                      |                                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +
> +Runtime configuration
> +---------------------
> +
> +
> +Created graph for use case
> +--------------------------
> +
> +On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
> +This section mentions the created graph for each use case.
> diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> index f2afb1fcc5..4f4dc8b518 100644
> --- a/doc/guides/tools/index.rst
> +++ b/doc/guides/tools/index.rst
> @@ -23,4 +23,5 @@ DPDK Tools User Guides
>      testeventdev
>      testregex
>      testmldev
> +    graph
>      dts
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 02/12] app/graph: support telnet connectivity framework
  2023-10-19 17:30                                   ` [PATCH v11 02/12] app/graph: support telnet connectivity framework skori
@ 2023-10-23  7:03                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:03 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev, Jerin Jacob

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 3:47 AM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds framework to initiate a telnet session with application.
>
> Some configurations and debug commands are exposed as runtime APIs.
> Those commands can be invoked using telnet session.
>
> Application initiates a telnet server with host address 0.0.0.0
> and port number 8086 by default.
>
> To make it configurable, "-h" and "-p" options are provided.
> Using them user can pass host address and port number on which
> application will start telnet server.
>
> Using same host address and port number, telnet client can connect
> to application.
>
> Syntax to connect with application:
>         # telnet <host> <port>
>
> Once session is connected, "graph> " prompt will be available.
> Example:
>         # telnet 10.28.35.207 50000
>           Trying 10.28.35.207...
>           Connected to 10.28.35.207.
>           Escape character is '^]'.
>
>           Welcome!
>
>           graph>
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/conn.c           | 284 +++++++++++++++++++++++++++++++++++++
>  app/graph/conn.h           |  46 ++++++
>  app/graph/main.c           | 103 +++++++++++++-
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   3 +
>  doc/guides/tools/graph.rst |  38 +++++
>  6 files changed, 470 insertions(+), 5 deletions(-)
>  create mode 100644 app/graph/conn.c
>  create mode 100644 app/graph/conn.h
>
> diff --git a/app/graph/conn.c b/app/graph/conn.c
> new file mode 100644
> index 0000000000..44934602c7
> --- /dev/null
> +++ b/app/graph/conn.c
> @@ -0,0 +1,284 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <netinet/in.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#include <rte_string_fns.h>
> +
> +#include "module_api.h"
> +
> +#define MSG_CMD_TOO_LONG "Command too long."
> +
> +static int
> +data_event_handle(struct conn *conn, int fd_client)
> +{
> +       ssize_t len, i, rc = 0;
> +
> +       /* Read input message */
> +       len = read(fd_client, conn->buf, conn->buf_size);
> +       if (len == -1) {
> +               if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +                       return 0;
> +
> +               return -1;
> +       }
> +
> +       if (len == 0)
> +               return rc;
> +
> +       /* Handle input messages */
> +       for (i = 0; i < len; i++) {
> +               if (conn->buf[i] == '\n') {
> +                       size_t n;
> +
> +                       conn->msg_in[conn->msg_in_len] = 0;
> +                       conn->msg_out[0] = 0;
> +
> +                       conn->msg_handle(conn->msg_in, conn->msg_out, conn->msg_out_len_max,
> +                                        conn->msg_handle_arg);
> +
> +                       n = strlen(conn->msg_out);
> +                       if (n) {
> +                               rc = write(fd_client, conn->msg_out, n);
> +                               if (rc == -1)
> +                                       goto exit;
> +                       }
> +
> +                       conn->msg_in_len = 0;
> +               } else if (conn->msg_in_len < conn->msg_in_len_max) {
> +                       conn->msg_in[conn->msg_in_len] = conn->buf[i];
> +                       conn->msg_in_len++;
> +               } else {
> +                       rc = write(fd_client, MSG_CMD_TOO_LONG, strlen(MSG_CMD_TOO_LONG));
> +                       if (rc == -1)
> +                               goto exit;
> +
> +                       conn->msg_in_len = 0;
> +               }
> +       }
> +
> +       /* Write prompt */
> +       rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +       rc = (rc == -1) ? -1 : 0;
> +
> +exit:
> +       return rc;
> +}
> +
> +static int
> +control_event_handle(struct conn *conn, int fd_client)
> +{
> +       int rc;
> +
> +       rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
> +       if (rc == -1)
> +               goto exit;
> +
> +       rc = close(fd_client);
> +       if (rc == -1)
> +               goto exit;
> +
> +       rc = 0;
> +
> +exit:
> +       return rc;
> +}
> +
> +struct conn *
> +conn_init(struct conn_params *p)
> +{
> +       int fd_server, fd_client_group, rc;
> +       struct sockaddr_in server_address;
> +       struct conn *conn = NULL;
> +       int reuse = 1;
> +
> +       memset(&server_address, 0, sizeof(server_address));
> +
> +       /* Check input arguments */
> +       if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr == NULL) ||
> +           (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0) ||
> +           (p->msg_handle == NULL))
> +               goto exit;
> +
> +       rc = inet_aton(p->addr, &server_address.sin_addr);
> +       if (rc == 0)
> +               goto exit;
> +
> +       /* Memory allocation */
> +       conn = calloc(1, sizeof(struct conn));
> +       if (conn == NULL)
> +               goto exit;
> +
> +       conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
> +       conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
> +       conn->buf = calloc(1, p->buf_size);
> +       conn->msg_in = calloc(1, p->msg_in_len_max + 1);
> +       conn->msg_out = calloc(1, p->msg_out_len_max + 1);
> +
> +       if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL) ||
> +           (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
> +               conn_free(conn);
> +               conn = NULL;
> +               goto exit;
> +       }
> +
> +       /* Server socket */
> +       server_address.sin_family = AF_INET;
> +       server_address.sin_port = htons(p->port);
> +
> +       fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
> +       if (fd_server == -1) {
> +               conn_free(conn);
> +               conn = NULL;
> +               goto exit;
> +       }
> +
> +       if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
> +                      sizeof(reuse)) < 0)
> +               goto free;
> +
> +       rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
> +       if (rc == -1)
> +               goto free;
> +
> +       rc = listen(fd_server, 16);
> +       if (rc == -1)
> +               goto free;
> +
> +       /* Client group */
> +       fd_client_group = epoll_create(1);
> +       if (fd_client_group == -1)
> +               goto free;
> +
> +       /* Fill in */
> +       rte_strscpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> +       rte_strscpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
> +       conn->buf_size = p->buf_size;
> +       conn->msg_in_len_max = p->msg_in_len_max;
> +       conn->msg_out_len_max = p->msg_out_len_max;
> +       conn->msg_in_len = 0;
> +       conn->fd_server = fd_server;
> +       conn->fd_client_group = fd_client_group;
> +       conn->msg_handle = p->msg_handle;
> +       conn->msg_handle_arg = p->msg_handle_arg;
> +
> +exit:
> +       return conn;
> +free:
> +       conn_free(conn);
> +       close(fd_server);
> +       conn = NULL;
> +       return conn;
> +}
> +
> +void
> +conn_free(struct conn *conn)
> +{
> +       if (conn == NULL)
> +               return;
> +
> +       if (conn->fd_client_group)
> +               close(conn->fd_client_group);
> +
> +       if (conn->fd_server)
> +               close(conn->fd_server);
> +
> +       free(conn->msg_out);
> +       free(conn->msg_in);
> +       free(conn->prompt);
> +       free(conn->welcome);
> +       free(conn);
> +}
> +
> +int
> +conn_req_poll(struct conn *conn)
> +{
> +       struct sockaddr_in client_address;
> +       socklen_t client_address_length;
> +       struct epoll_event event;
> +       int fd_client, rc;
> +
> +       /* Check input arguments */
> +       if (conn == NULL)
> +               return -1;
> +
> +       /* Server socket */
> +       client_address_length = sizeof(client_address);
> +       fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
> +                           &client_address_length, SOCK_NONBLOCK);
> +       if (fd_client == -1) {
> +               if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> +                       return 0;
> +
> +               return -1;
> +       }
> +
> +       /* Client group */
> +       event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
> +       event.data.fd = fd_client;
> +
> +       rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
> +       if (rc == -1) {
> +               close(fd_client);
> +               goto exit;
> +       }
> +
> +       /* Client */
> +       rc = write(fd_client, conn->welcome, strlen(conn->welcome));
> +       if (rc == -1) {
> +               close(fd_client);
> +               goto exit;
> +       }
> +
> +       rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> +       if (rc == -1) {
> +               close(fd_client);
> +               goto exit;
> +       }
> +
> +       rc = 0;
> +
> +exit:
> +       return rc;
> +}
> +
> +int
> +conn_msg_poll(struct conn *conn)
> +{
> +       int fd_client, rc, rc_data = 0, rc_control = 0;
> +       struct epoll_event event;
> +
> +       /* Check input arguments */
> +       if (conn == NULL)
> +               return -1;
> +
> +       /* Client group */
> +       rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
> +       if ((rc == -1) || rc == 0)
> +               return rc;
> +
> +       fd_client = event.data.fd;
> +
> +       /* Data available */
> +       if (event.events & EPOLLIN)
> +               rc_data = data_event_handle(conn, fd_client);
> +
> +       /* Control events */
> +       if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
> +               rc_control = control_event_handle(conn, fd_client);
> +
> +       if (rc_data || rc_control)
> +               return -1;
> +
> +       return 0;
> +}
> diff --git a/app/graph/conn.h b/app/graph/conn.h
> new file mode 100644
> index 0000000000..770964cf4c
> --- /dev/null
> +++ b/app/graph/conn.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CONN_H
> +#define APP_GRAPH_CONN_H
> +
> +#define CONN_WELCOME_LEN_MAX 1024
> +#define CONN_PROMPT_LEN_MAX 16
> +
> +typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t msg_out_len_max, void *arg);
> +
> +struct conn {
> +       char *welcome;
> +       char *prompt;
> +       char *buf;
> +       char *msg_in;
> +       char *msg_out;
> +       size_t buf_size;
> +       size_t msg_in_len_max;
> +       size_t msg_out_len_max;
> +       size_t msg_in_len;
> +       int fd_server;
> +       int fd_client_group;
> +       conn_msg_handle_t msg_handle;
> +       void *msg_handle_arg;
> +};
> +
> +struct conn_params {
> +       const char *welcome;
> +       const char *prompt;
> +       const char *addr;
> +       uint16_t port;
> +       size_t buf_size;
> +       size_t msg_in_len_max;
> +       size_t msg_out_len_max;
> +       conn_msg_handle_t msg_handle;
> +       void *msg_handle_arg;
> +};
> +
> +struct conn *conn_init(struct conn_params *p);
> +void conn_free(struct conn *conn);
> +int conn_req_poll(struct conn *conn);
> +int conn_msg_poll(struct conn *conn);
> +
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> index 734a94444e..96548f49e7 100644
> --- a/app/graph/main.c
> +++ b/app/graph/main.c
> @@ -2,6 +2,7 @@
>   * Copyright(c) 2023 Marvell.
>   */
>
> +#include <errno.h>
>  #include <fcntl.h>
>  #include <getopt.h>
>  #include <signal.h>
> @@ -11,19 +12,33 @@
>  #include <sys/select.h>
>  #include <unistd.h>
>
> +#include <rte_cycles.h>
>  #include <rte_eal.h>
>  #include <rte_launch.h>
>
>  #include "module_api.h"
>
>  volatile bool force_quit;
> +struct conn *conn;
>
> -static const char usage[] = "%s EAL_ARGS -- -s SCRIPT "
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
>                             "[--help]\n";
>
>  static struct app_params {
> +       struct conn_params conn;
>         char *script_name;
>  } app = {
> +       .conn = {
> +               .welcome = "\nWelcome!\n\n",
> +               .prompt = "graph> ",
> +               .addr = "0.0.0.0",
> +               .port = 8086,
> +               .buf_size = 1024 * 1024,
> +               .msg_in_len_max = 1024,
> +               .msg_out_len_max = 1024 * 1024,
> +               .msg_handle = cli_process,
> +               .msg_handle_arg = NULL, /* set later. */
> +       },
>         .script_name = NULL,
>  };
>
> @@ -42,7 +57,7 @@ app_args_parse(int argc, char **argv)
>         struct option lgopts[] = {
>                 {"help", 0, 0, 'H'},
>         };
> -       int s_present, n_args, i;
> +       int h_present, p_present, s_present, n_args, i;
>         char *app_name = argv[0];
>         int opt, option_index;
>
> @@ -59,10 +74,46 @@ app_args_parse(int argc, char **argv)
>                 return 0;
>
>         /* Parse args */
> +       h_present = 0;
> +       p_present = 0;
>         s_present = 0;
>
> -       while ((opt = getopt_long(argc, argv, "s:", lgopts, &option_index)) != EOF) {
> +       while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
>                 switch (opt) {
> +               case 'h':
> +                       if (h_present) {
> +                               printf("Error: Multiple -h arguments\n");
> +                               return -1;
> +                       }
> +                       h_present = 1;
> +
> +                       if (!strlen(optarg)) {
> +                               printf("Error: Argument for -h not provided\n");
> +                               return -1;
> +                       }
> +
> +                       app.conn.addr = strdup(optarg);
> +                       if (app.conn.addr == NULL) {
> +                               printf("Error: Not enough memory\n");
> +                               return -1;
> +                       }
> +                       break;
> +
> +               case 'p':
> +                       if (p_present) {
> +                               printf("Error: Multiple -p arguments\n");
> +                               return -1;
> +                       }
> +                       p_present = 1;
> +
> +                       if (!strlen(optarg)) {
> +                               printf("Error: Argument for -p not provided\n");
> +                               return -1;
> +                       }
> +
> +                       app.conn.port = (uint16_t)strtoul(optarg, NULL, 10);
> +                       break;
> +
>                 case 's':
>                         if (s_present) {
>                                 printf("Error: Multiple -s arguments\n");
> @@ -93,6 +144,26 @@ app_args_parse(int argc, char **argv)
>         return 0;
>  }
>
> +bool
> +app_graph_exit(void)
> +{
> +       struct timeval tv;
> +       fd_set fds;
> +       int ret;
> +       char c;
> +
> +       FD_ZERO(&fds);
> +       FD_SET(0, &fds);
> +       tv.tv_sec = 0;
> +       tv.tv_usec = 100;
> +       ret = select(1, &fds, NULL, NULL, &tv);
> +       if ((ret < 0 && errno == EINTR) || (ret == 1 && read(0, &c, 1) > 0))
> +               return true;
> +       else
> +               return false;
> +
> +}
> +
>  int
>  main(int argc, char **argv)
>  {
> @@ -118,10 +189,32 @@ main(int argc, char **argv)
>
>         /* Script */
>         if (app.script_name) {
> -               cli_script_process(app.script_name, 0,
> -                       0, NULL);
> +               cli_script_process(app.script_name, app.conn.msg_in_len_max,
> +                       app.conn.msg_out_len_max, NULL);
> +       }
> +
> +       /* Connectivity */
> +       app.conn.msg_handle_arg = NULL;
> +       conn = conn_init(&app.conn);
> +       if (!conn) {
> +               printf("Error: Connectivity initialization failed\n");
> +               goto exit;
> +       };
> +
> +       rte_delay_ms(1);
> +       printf("Press enter to exit\n");
> +
> +       /* Dispatch loop */
> +       while (!force_quit) {
> +               conn_req_poll(conn);
> +
> +               conn_msg_poll(conn);
> +               if (app_graph_exit())
> +                       force_quit = true;
>         }
>
> +exit:
> +       conn_free(conn);
>         cli_exit();
>         rte_eal_cleanup();
>         return 0;
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index ed33a04476..c8d2b41b69 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -11,5 +11,6 @@ endif
>  deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
>  sources = files(
>          'cli.c',
> +        'conn.c',
>          'main.c',
>  )
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index 372aeae7e3..9826303f0c 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -7,10 +7,13 @@
>
>  #include <stdint.h>
>  #include <stdbool.h>
> +
>  #include "cli.h"
> +#include "conn.h"
>  /*
>   * Externs
>   */
>  extern volatile bool force_quit;
>
> +bool app_graph_exit(void);
>  #endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index cb005e7856..943f915049 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -39,6 +39,16 @@ Application Options
>
>  Following are the application command-line options:
>
> +* ``-h``
> +
> +        Set the host IPv4 address over which telnet session can be opened.
> +        It is an optional parameter. Default host address is 0.0.0.0.
> +
> +* ``-p``
> +
> +        Set the L4 port number over which telnet session can be opened.
> +       It is an optional parameter. Default port is 8086.
> +
>  * ``-s``
>
>          Script name with absolute path which specifies the use case. It is
> @@ -67,7 +77,35 @@ file to express the requested use case configuration.
>  Runtime configuration
>  ---------------------
>
> +Application allows some configuration to be modified at runtime using a telnet session.
> +Application initiates a telnet server with host address ``0.0.0.0`` and port number ``8086``
> +by default.
> +
> +if user passes ``-h`` and ``-p`` options while running application then corresponding
> +IP address and port number will be used for telnet session.
> +
> +After successful launch of application, client can connect to application using given
> +host & port and console will be accessed with prompt ``graph>``.
> +
> +Command to access a telnet session
> +
> +.. code-block:: console
> +
> +   telnet <host> <port>
> +
> +Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
> +
> +.. code-block:: console
> +
> +   $ telnet 10.28.35.207 50000
> +   Trying 10.28.35.207...
> +   Connected to 10.28.35.207.
> +   Escape character is '^]'.
> +
> +   Welcome!
>
> +   graph>
> +   graph>
>  Created graph for use case
>  --------------------------
>
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 03/12] app/graph: support parser utility APIs
  2023-10-19 17:30                                   ` [PATCH v11 03/12] app/graph: support parser utility APIs skori
@ 2023-10-23  7:03                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:03 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev, Jerin Jacob

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Thu, Oct 19, 2023 at 11:00 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds some helper functions to parse IPv4, IPv6 and MAC addresses
> string into respective datatype.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/meson.build  |   1 +
>  app/graph/module_api.h |   1 +
>  app/graph/utils.c      | 156 +++++++++++++++++++++++++++++++++++++++++
>  app/graph/utils.h      |  14 ++++
>  4 files changed, 172 insertions(+)
>  create mode 100644 app/graph/utils.c
>  create mode 100644 app/graph/utils.h
>
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index c8d2b41b69..fd71036a95 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -13,4 +13,5 @@ sources = files(
>          'cli.c',
>          'conn.c',
>          'main.c',
> +        'utils.c',
>  )
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index 9826303f0c..ad4fb50989 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -10,6 +10,7 @@
>
>  #include "cli.h"
>  #include "conn.h"
> +#include "utils.h"
>  /*
>   * Externs
>   */
> diff --git a/app/graph/utils.c b/app/graph/utils.c
> new file mode 100644
> index 0000000000..c7b6ae83cf
> --- /dev/null
> +++ b/app/graph/utils.c
> @@ -0,0 +1,156 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +
> +#include "module_api.h"
> +
> +#define white_spaces_skip(pos)                 \
> +({                                             \
> +       __typeof__(pos) _p = (pos);             \
> +       for ( ; isspace(*_p); _p++)             \
> +               ;                               \
> +       _p;                                     \
> +})
> +
> +static void
> +hex_string_to_uint64(uint64_t *dst, const char *hexs)
> +{
> +       char buf[2] = {0};
> +       uint8_t shift = 4;
> +       int iter = 0;
> +       char c;
> +
> +       while ((c = *hexs++)) {
> +               buf[0] = c;
> +               *dst |= (strtol(buf, NULL, 16) << shift);
> +               shift -= 4;
> +               iter++;
> +               if (iter == 2) {
> +                       iter = 0;
> +                       shift = 4;
> +                       dst++;
> +               }
> +       }
> +}
> +
> +int
> +parser_uint64_read(uint64_t *value, const char *p)
> +{
> +       char *next;
> +       uint64_t val;
> +
> +       p = white_spaces_skip(p);
> +       if (!isdigit(*p))
> +               return -EINVAL;
> +
> +       val = strtoul(p, &next, 0);
> +       if (p == next)
> +               return -EINVAL;
> +
> +       p = next;
> +       switch (*p) {
> +       case 'T':
> +               val *= 1024ULL;
> +               /* fall through */
> +       case 'G':
> +               val *= 1024ULL;
> +               /* fall through */
> +       case 'M':
> +               val *= 1024ULL;
> +               /* fall through */
> +       case 'k':
> +       case 'K':
> +               val *= 1024ULL;
> +               p++;
> +               break;
> +       }
> +
> +       p = white_spaces_skip(p);
> +       if (*p != '\0')
> +               return -EINVAL;
> +
> +       *value = val;
> +       return 0;
> +}
> +
> +int
> +parser_uint32_read(uint32_t *value, const char *p)
> +{
> +       uint64_t val = 0;
> +       int rc = parser_uint64_read(&val, p);
> +
> +       if (rc < 0)
> +               return rc;
> +
> +       if (val > UINT32_MAX)
> +               return -ERANGE;
> +
> +       *value = val;
> +       return 0;
> +}
> +
> +int
> +parser_ip4_read(uint32_t *value, char *p)
> +{
> +       uint8_t shift = 24;
> +       uint32_t ip = 0;
> +       char *token;
> +
> +       token = strtok(p, ".");
> +       while (token != NULL) {
> +               ip |= (((uint32_t)strtoul(token, NULL, 10)) << shift);
> +               token = strtok(NULL, ".");
> +               shift -= 8;
> +       }
> +
> +       *value = ip;
> +
> +       return 0;
> +}
> +
> +int
> +parser_ip6_read(uint8_t *value, char *p)
> +{
> +       uint64_t val = 0;
> +       char *token;
> +
> +       token = strtok(p, ":");
> +       while (token != NULL) {
> +               hex_string_to_uint64(&val, token);
> +               *value = val;
> +               token = strtok(NULL, ":");
> +               value++;
> +               val = 0;
> +       }
> +
> +       return 0;
> +}
> +
> +int
> +parser_mac_read(uint64_t *value, char *p)
> +{
> +       uint64_t mac = 0, val = 0;
> +       uint8_t shift = 40;
> +       char *token;
> +
> +       token = strtok(p, ":");
> +       while (token != NULL) {
> +               hex_string_to_uint64(&val, token);
> +               mac |= val << shift;
> +               token = strtok(NULL, ":");
> +               shift -= 8;
> +               val = 0;
> +       }
> +
> +       *value = mac;
> +
> +       return 0;
> +}
> diff --git a/app/graph/utils.h b/app/graph/utils.h
> new file mode 100644
> index 0000000000..0ebb5de55a
> --- /dev/null
> +++ b/app/graph/utils.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_UTILS_H
> +#define APP_GRAPH_UTILS_H
> +
> +int parser_uint64_read(uint64_t *value, const char *p);
> +int parser_uint32_read(uint32_t *value, const char *p);
> +int parser_ip4_read(uint32_t *value, char *p);
> +int parser_ip6_read(uint8_t *value, char *p);
> +int parser_mac_read(uint64_t *value, char *p);
> +
> +#endif
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 04/12] app/graph: support mempool command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 04/12] app/graph: support mempool command line interfaces skori
@ 2023-10-23  7:04                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:04 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev, Jerin Jacob

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Thu, Oct 19, 2023 at 11:00 PM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> Adds mempool module which will be creating mempools.
>
> Following commands are exposed:
>  - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> \
>         cache <cache_size> numa <numa_id>
>  - help mempool
>
> User will add this command in .cli file according to its need.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/cli.c            |   2 +
>  app/graph/mempool.c        | 140 +++++++++++++++++++++++++++++++++++++
>  app/graph/mempool.h        |  24 +++++++
>  app/graph/mempool_priv.h   |  34 +++++++++
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   2 +
>  doc/guides/tools/graph.rst |   7 ++
>  7 files changed, 210 insertions(+)
>  create mode 100644 app/graph/mempool.c
>  create mode 100644 app/graph/mempool.h
>  create mode 100644 app/graph/mempool_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index df4f8fcbb8..cf544d5f8f 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -20,6 +20,8 @@
>  #define MAX_LINE_SIZE 2048
>
>  cmdline_parse_ctx_t modules_ctx[] = {
> +       (cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
> +       (cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/mempool.c b/app/graph/mempool.c
> new file mode 100644
> index 0000000000..9fd3f8460b
> --- /dev/null
> +++ b/app/graph/mempool.c
> @@ -0,0 +1,140 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_common.h>
> +#include <rte_mbuf.h>
> +
> +#include "mempool_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> "
> +                    "cache <cache_size> numa <numa_id>";
> +
> +struct mempools mpconfig;
> +
> +int
> +mempool_process(struct mempool_config *config)
> +{
> +       struct rte_mempool *mp;
> +       uint8_t nb_pools;
> +
> +       nb_pools = mpconfig.nb_pools;
> +       rte_strscpy(mpconfig.config[nb_pools].name, config->name, RTE_MEMPOOL_NAMESIZE);
> +       mpconfig.config[nb_pools].pool_size = config->pool_size;
> +       mpconfig.config[nb_pools].buffer_size = config->buffer_size;
> +       mpconfig.config[nb_pools].cache_size = config->cache_size;
> +       mpconfig.config[nb_pools].numa_node = config->numa_node;
> +
> +       mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config->cache_size,
> +               128, config->buffer_size, config->numa_node);
> +       if (!mp)
> +               return -EINVAL;
> +
> +       mpconfig.mp[nb_pools] = mp;
> +       nb_pools++;
> +       mpconfig.nb_pools = nb_pools;
> +
> +       return 0;
> +}
> +
> +static void
> +cli_mempool_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
> +                "----------------------------- mempool command help -----------------------------",
> +                cmd_mempool_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +static void
> +cli_mempool(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
> +{
> +       struct mempool_config_cmd_tokens *res = parsed_result;
> +       struct mempool_config config;
> +       int rc = -EINVAL;
> +
> +
> +       rte_strscpy(config.name, res->name, RTE_MEMPOOL_NAMESIZE);
> +       config.name[strlen(res->name)] = '\0';
> +       config.pool_size = res->nb_bufs;
> +       config.buffer_size = res->buf_sz;
> +       config.cache_size = res->cache_size;
> +       config.numa_node = res->node;
> +
> +       rc = mempool_process(&config);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, "mempool");
> +}
> +
> +cmdline_parse_token_string_t mempool_config_add_mempool =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, mempool, "mempool");
> +cmdline_parse_token_string_t mempool_config_add_name =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, name, NULL);
> +cmdline_parse_token_string_t mempool_config_add_size =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, size, "size");
> +cmdline_parse_token_num_t mempool_config_add_buf_sz =
> +       TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, buf_sz, RTE_UINT16);
> +cmdline_parse_token_string_t mempool_config_add_buffers =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, buffers, "buffers");
> +cmdline_parse_token_num_t mempool_config_add_nb_bufs =
> +       TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, nb_bufs, RTE_UINT16);
> +cmdline_parse_token_string_t mempool_config_add_cache =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, cache, "cache");
> +cmdline_parse_token_num_t mempool_config_add_cache_size =
> +       TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, cache_size, RTE_UINT16);
> +cmdline_parse_token_string_t mempool_config_add_numa =
> +       TOKEN_STRING_INITIALIZER(struct mempool_config_cmd_tokens, numa, "numa");
> +cmdline_parse_token_num_t mempool_config_add_node =
> +       TOKEN_NUM_INITIALIZER(struct mempool_config_cmd_tokens, node, RTE_UINT16);
> +
> +cmdline_parse_inst_t mempool_config_cmd_ctx = {
> +       .f = cli_mempool,
> +       .data = NULL,
> +       .help_str = cmd_mempool_help,
> +       .tokens = {
> +               (void *)&mempool_config_add_mempool,
> +               (void *)&mempool_config_add_name,
> +               (void *)&mempool_config_add_size,
> +               (void *)&mempool_config_add_buf_sz,
> +               (void *)&mempool_config_add_buffers,
> +               (void *)&mempool_config_add_nb_bufs,
> +               (void *)&mempool_config_add_cache,
> +               (void *)&mempool_config_add_cache_size,
> +               (void *)&mempool_config_add_numa,
> +               (void *)&mempool_config_add_node,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t mempool_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, help, "help");
> +cmdline_parse_token_string_t mempool_help_mempool =
> +       TOKEN_STRING_INITIALIZER(struct mempool_help_cmd_tokens, mempool, "mempool");
> +
> +cmdline_parse_inst_t mempool_help_cmd_ctx = {
> +       .f = cli_mempool_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&mempool_help_cmd,
> +               (void *)&mempool_help_mempool,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/mempool.h b/app/graph/mempool.h
> new file mode 100644
> index 0000000000..0808c4259e
> --- /dev/null
> +++ b/app/graph/mempool.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_H
> +#define APP_GRAPH_MEMPOOL_H
> +
> +#include <cmdline_parse.h>
> +#include <rte_mempool.h>
> +
> +struct mempool_config {
> +       char name[RTE_MEMPOOL_NAMESIZE];
> +       int pool_size;
> +       int cache_size;
> +       int buffer_size;
> +       int numa_node;
> +};
> +
> +extern cmdline_parse_inst_t mempool_config_cmd_ctx;
> +extern cmdline_parse_inst_t mempool_help_cmd_ctx;
> +
> +int mempool_process(struct mempool_config *config);
> +
> +#endif
> diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
> new file mode 100644
> index 0000000000..3ce64702a9
> --- /dev/null
> +++ b/app/graph/mempool_priv.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_PRIV_H
> +#define APP_GRAPH_MEMPOOL_PRIV_H
> +
> +#include "mempool.h"
> +
> +struct mempool_config_cmd_tokens {
> +       cmdline_fixed_string_t mempool;
> +       cmdline_fixed_string_t size;
> +       cmdline_fixed_string_t buffers;
> +       cmdline_fixed_string_t cache;
> +       cmdline_fixed_string_t numa;
> +       cmdline_fixed_string_t name;
> +       uint16_t buf_sz;
> +       uint16_t nb_bufs;
> +       uint16_t cache_size;
> +       uint16_t node;
> +};
> +
> +struct mempool_help_cmd_tokens {
> +       cmdline_fixed_string_t help;
> +       cmdline_fixed_string_t mempool;
> +};
> +
> +struct mempools {
> +       struct mempool_config config[RTE_MAX_ETHPORTS];
> +       struct rte_mempool *mp[RTE_MAX_ETHPORTS];
> +       uint8_t nb_pools;
> +};
> +
> +#endif
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index fd71036a95..5dc23c875b 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -13,5 +13,6 @@ sources = files(
>          'cli.c',
>          'conn.c',
>          'main.c',
> +        'mempool.c',
>          'utils.c',
>  )
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index ad4fb50989..b45419811b 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -10,11 +10,13 @@
>
>  #include "cli.h"
>  #include "conn.h"
> +#include "mempool.h"
>  #include "utils.h"
>  /*
>   * Externs
>   */
>  extern volatile bool force_quit;
> +extern struct conn *conn;
>
>  bool app_graph_exit(void);
>  #endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 943f915049..6009b0c291 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -71,6 +71,13 @@ file to express the requested use case configuration.
>     +--------------------------------------+-----------------------------------+---------+----------+
>     |               Command                |             Description           | Dynamic | Optional |
>     +======================================+===================================+=========+==========+
> +   | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
> +   | | <mbuf_size> buffers                | | will be further associated to   |         |          |
> +   | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
> +   | | cache <cache_size> numa <numa_id>  |                                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
> +   |                                      | | message.                        |         |          |
>     |                                      |                                   |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
>
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 05/12] app/graph: support ethdev command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 05/12] app/graph: support ethdev " skori
@ 2023-10-23  7:04                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:04 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev, Jerin Jacob

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 3:47 AM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds ethdev module to configure ethernet devices.
>
> Following commands are exposed:
>  - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
>  - ethdev <ethdev_name> mtu <mtu_sz>
>  - ethdev <ethdev_name> promiscuous <on/off>
>  - ethdev <ethdev_name> show
>  - ethdev <ethdev_name> stats
>  - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
>  - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
>  - help ethdev
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/cli.c            |   8 +
>  app/graph/ethdev.c         | 887 +++++++++++++++++++++++++++++++++++++
>  app/graph/ethdev.h         |  40 ++
>  app/graph/ethdev_priv.h    | 112 +++++
>  app/graph/main.c           |   1 +
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   1 +
>  doc/guides/tools/graph.rst |  50 ++-
>  8 files changed, 1099 insertions(+), 1 deletion(-)
>  create mode 100644 app/graph/ethdev.c
>  create mode 100644 app/graph/ethdev.h
>  create mode 100644 app/graph/ethdev_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index cf544d5f8f..fa394fade6 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -22,6 +22,14 @@
>  cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
>         (cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_stats_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_mtu_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_prom_mode_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_ip4_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> new file mode 100644
> index 0000000000..8df55b4b12
> --- /dev/null
> +++ b/app/graph/ethdev.c
> @@ -0,0 +1,887 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_bitops.h>
> +#include <rte_ethdev.h>
> +#include <rte_mempool.h>
> +
> +#include "ethdev_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
> +
> +static const char
> +cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
> +
> +static const char
> +cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>";
> +
> +static const char
> +cmd_ethdev_stats_help[] = "ethdev <ethdev_name> stats";
> +
> +static const char
> +cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
> +
> +static const char
> +cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>";
> +
> +static const char
> +cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>";
> +
> +static struct rte_eth_conf port_conf_default = {
> +       .link_speeds = 0,
> +       .rxmode = {
> +               .mq_mode = RTE_ETH_MQ_RX_NONE,
> +               .mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo frame MTU */
> +       },
> +       .rx_adv_conf = {
> +               .rss_conf = {
> +                       .rss_key = NULL,
> +                       .rss_key_len = 40,
> +                       .rss_hf = 0,
> +               },
> +       },
> +       .txmode = {
> +               .mq_mode = RTE_ETH_MQ_TX_NONE,
> +       },
> +       .lpbk_mode = 0,
> +};
> +
> +uint32_t enabled_port_mask;
> +static struct ethdev_head eth_node = TAILQ_HEAD_INITIALIZER(eth_node);
> +
> +
> +static struct ethdev*
> +ethdev_port_by_id(uint16_t port_id)
> +{
> +       struct ethdev *port;
> +
> +       TAILQ_FOREACH(port, &eth_node, next) {
> +               if (port->config.port_id == port_id)
> +                       return port;
> +       }
> +       return NULL;
> +}
> +
> +void *
> +ethdev_mempool_list_by_portid(uint16_t portid)
> +{
> +       struct ethdev *port;
> +
> +       if (portid >= RTE_MAX_ETHPORTS)
> +               return NULL;
> +
> +       port = ethdev_port_by_id(portid);
> +       if (port)
> +               return &(port->config.rx.mp);
> +       else
> +               return NULL;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip4(uint32_t ip, uint32_t mask)
> +{
> +       int portid = -EINVAL;
> +       struct ethdev *port;
> +
> +       TAILQ_FOREACH(port, &eth_node, next) {
> +               if (mask == 0) {
> +                       if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & port->ip4_addr.mask))
> +                               return port->config.port_id;
> +               } else {
> +                       if ((port->ip4_addr.ip & port->ip4_addr.mask) == (ip & mask))
> +                               return port->config.port_id;
> +               }
> +       }
> +
> +       return portid;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask)
> +{
> +       int portid = -EINVAL;
> +       struct ethdev *port;
> +       int j;
> +
> +       TAILQ_FOREACH(port, &eth_node, next) {
> +               for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +                       if (mask == NULL) {
> +                               if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
> +                                   (ip[j] & port->ip6_addr.mask[j]))
> +                                       break;
> +
> +                       } else {
> +                               if ((port->ip6_addr.ip[j] & port->ip6_addr.mask[j]) !=
> +                                   (ip[j] & mask[j]))
> +                                       break;
> +                       }
> +               }
> +               if (j == ETHDEV_IPV6_ADDR_LEN)
> +                       return port->config.port_id;
> +       }
> +
> +       return portid;
> +}
> +
> +void
> +ethdev_list_clean(void)
> +{
> +       struct ethdev *port;
> +
> +       while (!TAILQ_EMPTY(&eth_node)) {
> +               port = TAILQ_FIRST(&eth_node);
> +               TAILQ_REMOVE(&eth_node, port, next);
> +       }
> +}
> +
> +void
> +ethdev_stop(void)
> +{
> +       uint16_t portid;
> +       int rc;
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               if ((enabled_port_mask & (1 << portid)) == 0)
> +                       continue;
> +               printf("Closing port %d...", portid);
> +               rc = rte_eth_dev_stop(portid);
> +               if (rc != 0)
> +                       printf("Failed to stop port %u: %s\n",
> +                                       portid, rte_strerror(-rc));
> +               rte_eth_dev_close(portid);
> +               printf(" Done\n");
> +       }
> +
> +       ethdev_list_clean();
> +       rte_eal_cleanup();
> +       printf("Bye...\n");
> +}
> +
> +void
> +ethdev_start(void)
> +{
> +       uint16_t portid;
> +       int rc;
> +
> +       RTE_ETH_FOREACH_DEV(portid)
> +       {
> +               if ((enabled_port_mask & (1 << portid)) == 0)
> +                       continue;
> +
> +               rc = rte_eth_dev_start(portid);
> +               if (rc < 0)
> +                       rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n", rc, portid);
> +       }
> +}
> +
> +
> +static int
> +ethdev_show(const char *name)
> +{
> +       uint16_t mtu = 0, port_id = 0;
> +       struct rte_eth_dev_info info;
> +       struct rte_eth_stats stats;
> +       struct rte_ether_addr addr;
> +       struct rte_eth_link link;
> +       uint32_t length;
> +       int rc;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +       if (rc < 0)
> +               return rc;
> +
> +       rte_eth_dev_info_get(port_id, &info);
> +       rte_eth_stats_get(port_id, &stats);
> +       rte_eth_macaddr_get(port_id, &addr);
> +       rte_eth_link_get(port_id, &link);
> +       rte_eth_dev_get_mtu(port_id, &mtu);
> +
> +       length = strlen(conn->msg_out);
> +       conn->msg_out += length;
> +       snprintf(conn->msg_out, conn->msg_out_len_max,
> +                "%s: flags=<%s> mtu %u\n"
> +                "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
> +                "\tport# %u  speed %s\n"
> +                "\tRX packets %" PRIu64"  bytes %" PRIu64"\n"
> +                "\tRX errors %" PRIu64"  missed %" PRIu64"  no-mbuf %" PRIu64"\n"
> +                "\tTX packets %" PRIu64"  bytes %" PRIu64"\n"
> +                "\tTX errors %" PRIu64"\n\n",
> +                name,
> +                link.link_status ? "UP" : "DOWN",
> +                mtu,
> +                RTE_ETHER_ADDR_BYTES(&addr),
> +                info.nb_rx_queues,
> +                info.nb_tx_queues,
> +                port_id,
> +                rte_eth_link_speed_to_str(link.link_speed),
> +                stats.ipackets,
> +                stats.ibytes,
> +                stats.ierrors,
> +                stats.imissed,
> +                stats.rx_nombuf,
> +                stats.opackets,
> +                stats.obytes,
> +                stats.oerrors);
> +
> +       length = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= length;
> +       return 0;
> +}
> +
> +static int
> +ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
> +{
> +       struct ethdev *eth_hdl;
> +       uint16_t portid = 0;
> +       int rc;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &portid);
> +       if (rc < 0)
> +               return rc;
> +
> +       eth_hdl = ethdev_port_by_id(portid);
> +
> +       if (eth_hdl) {
> +               eth_hdl->ip4_addr.ip = config->ip;
> +               eth_hdl->ip4_addr.mask = config->mask;
> +               return 0;
> +       }
> +
> +       rc = -EINVAL;
> +       return rc;
> +}
> +
> +static int
> +ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
> +{
> +       struct ethdev *eth_hdl;
> +       uint16_t portid = 0;
> +       int rc, i;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &portid);
> +       if (rc < 0)
> +               return rc;
> +
> +       eth_hdl = ethdev_port_by_id(portid);
> +
> +       if (eth_hdl) {
> +               for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +                       eth_hdl->ip6_addr.ip[i] = config->ip[i];
> +                       eth_hdl->ip6_addr.mask[i] = config->mask[i];
> +               }
> +               return 0;
> +       }
> +       rc = -EINVAL;
> +       return rc;
> +}
> +
> +static int
> +ethdev_prom_mode_config(const char *name, bool enable)
> +{
> +       struct ethdev *eth_hdl;
> +       uint16_t portid = 0;
> +       int rc;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &portid);
> +       if (rc < 0)
> +               return rc;
> +
> +       eth_hdl = ethdev_port_by_id(portid);
> +
> +       if (eth_hdl) {
> +               if (enable)
> +                       rc = rte_eth_promiscuous_enable(portid);
> +               else
> +                       rc = rte_eth_promiscuous_disable(portid);
> +               if (rc < 0)
> +                       return rc;
> +
> +               eth_hdl->config.promiscuous = enable;
> +               return 0;
> +       }
> +
> +       rc = -EINVAL;
> +       return rc;
> +}
> +
> +static int
> +ethdev_mtu_config(const char *name, uint32_t mtu)
> +{
> +       struct ethdev *eth_hdl;
> +       uint16_t portid = 0;
> +       int rc;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &portid);
> +       if (rc < 0)
> +               return rc;
> +
> +       eth_hdl = ethdev_port_by_id(portid);
> +
> +       if (eth_hdl) {
> +               rc = rte_eth_dev_set_mtu(portid, mtu);
> +               if (rc < 0)
> +                       return rc;
> +
> +               eth_hdl->config.mtu = mtu;
> +               return 0;
> +       }
> +
> +       rc = -EINVAL;
> +       return rc;
> +}
> +
> +
> +static int
> +ethdev_process(const char *name, struct ethdev_config *params)
> +{
> +       struct rte_eth_dev_info port_info;
> +       struct rte_eth_conf port_conf;
> +       struct ethdev_rss_config *rss;
> +       struct rte_mempool *mempool;
> +       struct ethdev *ethdev_port;
> +       struct rte_ether_addr smac;
> +       uint16_t port_id = 0;
> +       int numa_node, rc;
> +       uint32_t i;
> +
> +       /* Check input params */
> +       if (!name || !name[0] || !params || !params->rx.n_queues || !params->rx.queue_size ||
> +           !params->tx.n_queues || !params->tx.queue_size)
> +               return -EINVAL;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +       if (rc)
> +               return -EINVAL;
> +
> +       if (!ethdev_port_by_id(port_id)) {
> +               ethdev_port = malloc(sizeof(struct ethdev));
> +               if (!ethdev_port)
> +                       return -EINVAL;
> +       } else {
> +               return 0;
> +       }
> +
> +       rc = rte_eth_dev_info_get(port_id, &port_info);
> +       if (rc) {
> +               rc = -EINVAL;
> +               goto exit;
> +       }
> +
> +       mempool = rte_mempool_lookup(params->rx.mempool_name);
> +       if (!mempool) {
> +               rc =  -EINVAL;
> +               goto exit;
> +       }
> +
> +       params->rx.mp = mempool;
> +
> +       rss = params->rx.rss;
> +       if (rss) {
> +               if (!port_info.reta_size || port_info.reta_size > RTE_ETH_RSS_RETA_SIZE_512) {
> +                       rc = -EINVAL;
> +                       goto exit;
> +               }
> +
> +               if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX) {
> +                       rc = -EINVAL;
> +                       goto exit;
> +               }
> +
> +               for (i = 0; i < rss->n_queues; i++)
> +                       if (rss->queue_id[i] >= port_info.max_rx_queues) {
> +                               rc = -EINVAL;
> +                               goto exit;
> +                       }
> +       }
> +
> +       /* Port */
> +       memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
> +       if (rss) {
> +               uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP;
> +
> +               port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
> +               port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & port_info.flow_type_rss_offloads;
> +       }
> +
> +       numa_node = rte_eth_dev_socket_id(port_id);
> +       if (numa_node == SOCKET_ID_ANY)
> +               numa_node = 0;
> +
> +       if (params->mtu)
> +               port_conf.rxmode.mtu = params->mtu;
> +
> +       rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
> +                                      &port_conf);
> +       if (rc < 0) {
> +               rc = -EINVAL;
> +               goto exit;
> +       }
> +
> +       rc = rte_eth_macaddr_get(port_id, &smac);
> +       if (rc < 0) {
> +               rc = -EINVAL;
> +               goto exit;
> +       }
> +
> +       printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
> +               smac.addr_bytes[0], smac.addr_bytes[1],
> +               smac.addr_bytes[2], smac.addr_bytes[3],
> +               smac.addr_bytes[4], smac.addr_bytes[5]);
> +
> +       /* Port RX */
> +       for (i = 0; i < params->rx.n_queues; i++) {
> +               rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size, numa_node, NULL,
> +                       mempool);
> +               if (rc < 0) {
> +                       rc = -EINVAL;
> +                       goto exit;
> +               }
> +       }
> +
> +       /* Port TX */
> +       for (i = 0; i < params->tx.n_queues; i++) {
> +               rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size, numa_node, NULL);
> +               if (rc < 0) {
> +                       rc = -EINVAL;
> +                       goto exit;
> +               }
> +       }
> +
> +       memcpy(&ethdev_port->config, params, sizeof(struct ethdev_config));
> +       memcpy(ethdev_port->config.dev_name, name, strlen(name));
> +       ethdev_port->config.port_id = port_id;
> +       enabled_port_mask |= RTE_BIT32(port_id);
> +
> +       TAILQ_INSERT_TAIL(&eth_node, ethdev_port, next);
> +       return 0;
> +exit:
> +       free(ethdev_port);
> +       return rc;
> +
> +}
> +
> +static int
> +ethdev_stats_show(const char *name)
> +{
> +       uint64_t diff_pkts_rx, diff_pkts_tx, diff_bytes_rx, diff_bytes_tx;
> +       static uint64_t prev_pkts_rx[RTE_MAX_ETHPORTS];
> +       static uint64_t prev_pkts_tx[RTE_MAX_ETHPORTS];
> +       static uint64_t prev_bytes_rx[RTE_MAX_ETHPORTS];
> +       static uint64_t prev_bytes_tx[RTE_MAX_ETHPORTS];
> +       static uint64_t prev_cycles[RTE_MAX_ETHPORTS];
> +       uint64_t mpps_rx, mpps_tx, mbps_rx, mbps_tx;
> +       uint64_t diff_ns, diff_cycles, curr_cycles;
> +       struct rte_eth_stats stats;
> +       static const char *nic_stats_border = "########################";
> +       uint16_t port_id, len;
> +       int rc;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +       if (rc < 0)
> +               return rc;
> +
> +       rc = rte_eth_stats_get(port_id, &stats);
> +       if (rc != 0) {
> +               fprintf(stderr,
> +                       "%s: Error: failed to get stats (port %u): %d",
> +                       __func__, port_id, rc);
> +               return rc;
> +       }
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max,
> +                "\n  %s NIC statistics for port %-2d %s\n"
> +                "  RX-packets: %-10"PRIu64" RX-missed: %-10"PRIu64" RX-bytes:  ""%-"PRIu64"\n"
> +                "  RX-errors: %-"PRIu64"\n"
> +                "  RX-nombuf:  %-10"PRIu64"\n"
> +                "  TX-packets: %-10"PRIu64" TX-errors: %-10"PRIu64" TX-bytes:  ""%-"PRIu64"\n",
> +                nic_stats_border, port_id, nic_stats_border, stats.ipackets, stats.imissed,
> +                stats.ibytes, stats.ierrors, stats.rx_nombuf, stats.opackets, stats.oerrors,
> +                stats.obytes);
> +
> +       len = strlen(conn->msg_out) - len;
> +       conn->msg_out_len_max -= len;
> +
> +       diff_ns = 0;
> +       diff_cycles = 0;
> +
> +       curr_cycles = rte_rdtsc();
> +       if (prev_cycles[port_id] != 0)
> +               diff_cycles = curr_cycles - prev_cycles[port_id];
> +
> +       prev_cycles[port_id] = curr_cycles;
> +       diff_ns = diff_cycles > 0 ?
> +               diff_cycles * (1 / (double)rte_get_tsc_hz()) * NS_PER_SEC : 0;
> +
> +       diff_pkts_rx = (stats.ipackets > prev_pkts_rx[port_id]) ?
> +               (stats.ipackets - prev_pkts_rx[port_id]) : 0;
> +       diff_pkts_tx = (stats.opackets > prev_pkts_tx[port_id]) ?
> +               (stats.opackets - prev_pkts_tx[port_id]) : 0;
> +       prev_pkts_rx[port_id] = stats.ipackets;
> +       prev_pkts_tx[port_id] = stats.opackets;
> +       mpps_rx = diff_ns > 0 ?
> +               (double)diff_pkts_rx / diff_ns * NS_PER_SEC : 0;
> +       mpps_tx = diff_ns > 0 ?
> +               (double)diff_pkts_tx / diff_ns * NS_PER_SEC : 0;
> +
> +       diff_bytes_rx = (stats.ibytes > prev_bytes_rx[port_id]) ?
> +               (stats.ibytes - prev_bytes_rx[port_id]) : 0;
> +       diff_bytes_tx = (stats.obytes > prev_bytes_tx[port_id]) ?
> +               (stats.obytes - prev_bytes_tx[port_id]) : 0;
> +       prev_bytes_rx[port_id] = stats.ibytes;
> +       prev_bytes_tx[port_id] = stats.obytes;
> +       mbps_rx = diff_ns > 0 ?
> +               (double)diff_bytes_rx / diff_ns * NS_PER_SEC : 0;
> +       mbps_tx = diff_ns > 0 ?
> +               (double)diff_bytes_tx / diff_ns * NS_PER_SEC : 0;
> +
> +       len = strlen(conn->msg_out);
> +       snprintf(conn->msg_out + len, conn->msg_out_len_max,
> +                "\n  Throughput (since last show)\n"
> +                "  Rx-pps: %12"PRIu64"          Rx-bps: %12"PRIu64"\n  Tx-pps: %12"
> +                PRIu64"          Tx-bps: %12"PRIu64"\n"
> +                "  %s############################%s\n",
> +                mpps_rx, mbps_rx * 8, mpps_tx, mbps_tx * 8, nic_stats_border, nic_stats_border);
> +       return 0;
> +}
> +
> +static void
> +cli_ethdev_mtu(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_mtu_cmd_tokens *res = parsed_result;
> +       int rc = -EINVAL;
> +
> +       rc = ethdev_mtu_config(res->dev, res->size);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_ethdev_prom_mode(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_prom_mode_cmd_tokens *res = parsed_result;
> +       bool enable = false;
> +       int rc = -EINVAL;
> +
> +       if (!strcmp(res->enable, "on"))
> +               enable = true;
> +
> +       rc = ethdev_prom_mode_config(res->dev, enable);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_ip4_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_ip4_cmd_tokens *res = parsed_result;
> +       struct ipv4_addr_config config;
> +       int rc = -EINVAL;
> +
> +       if (parser_ip4_read(&config.ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ip");
> +               return;
> +       }
> +
> +       if (parser_ip4_read(&config.mask, res->mask)) {
> +               printf(MSG_ARG_INVALID, "netmask");
> +               return;
> +       }
> +
> +       rc = ethdev_ip4_addr_add(res->dev, &config);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_ip6_addr(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_ip6_cmd_tokens *res = parsed_result;
> +       struct ipv6_addr_config config;
> +       int rc = -EINVAL;
> +
> +       if (parser_ip6_read(config.ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ip");
> +               return;
> +       }
> +
> +       if (parser_ip6_read(config.mask, res->mask)) {
> +               printf(MSG_ARG_INVALID, "netmask");
> +               return;
> +       }
> +
> +       rc = ethdev_ip6_addr_add(res->dev, &config);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_ethdev_show(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_show_cmd_tokens *res = parsed_result;
> +       int rc = -EINVAL;
> +
> +       rc = ethdev_show(res->dev);
> +       if (rc < 0)
> +               printf(MSG_ARG_INVALID, res->dev);
> +}
> +
> +static void
> +cli_ethdev_stats(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_stats_cmd_tokens *res = parsed_result;
> +       int rc = -EINVAL;
> +
> +       rc = ethdev_stats_show(res->dev);
> +       if (rc < 0)
> +               printf(MSG_ARG_INVALID, res->dev);
> +}
> +
> +static void
> +cli_ethdev(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_cmd_tokens *res = parsed_result;
> +       struct ethdev_config config;
> +       int rc;
> +
> +       memset(&config, 0, sizeof(struct ethdev_config));
> +       config.rx.n_queues = res->nb_rxq;
> +       config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
> +       memcpy(config.rx.mempool_name, res->mempool, strlen(res->mempool));
> +
> +       config.tx.n_queues = res->nb_txq;
> +       config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
> +
> +       config.mtu = port_conf_default.rxmode.mtu;
> +
> +       rc = ethdev_process(res->dev, &config);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_ethdev_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +               __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
> +                "----------------------------- ethdev command help -----------------------------",
> +                cmd_ethdev_help, cmd_ethdev_ip4_addr_help, cmd_ethdev_ip6_addr_help,
> +                cmd_ethdev_prom_mode_help, cmd_ethdev_mtu_help, cmd_ethdev_stats_help,
> +                cmd_ethdev_show_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +cmdline_parse_token_string_t ethdev_stats_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_stats_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_stats_stats =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_stats_cmd_tokens, stats, "stats");
> +
> +cmdline_parse_inst_t ethdev_stats_cmd_ctx = {
> +       .f = cli_ethdev_stats,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&ethdev_stats_cmd,
> +               (void *)&ethdev_stats_dev,
> +               (void *)&ethdev_stats_stats,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_show_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_show_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_show_show =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_show_cmd_tokens, show, "show");
> +
> +cmdline_parse_inst_t ethdev_show_cmd_ctx = {
> +       .f = cli_ethdev_show,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_show_help,
> +       .tokens = {
> +               (void *)&ethdev_show_cmd,
> +               (void *)&ethdev_show_dev,
> +               (void *)&ethdev_show_show,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_mtu_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_mtu_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_mtu_mtu =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_mtu_cmd_tokens, mtu, "mtu");
> +cmdline_parse_token_num_t ethdev_mtu_size =
> +       TOKEN_NUM_INITIALIZER(struct ethdev_mtu_cmd_tokens, size, RTE_UINT16);
> +
> +cmdline_parse_inst_t ethdev_mtu_cmd_ctx = {
> +       .f = cli_ethdev_mtu,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_mtu_help,
> +       .tokens = {
> +               (void *)&ethdev_mtu_cmd,
> +               (void *)&ethdev_mtu_dev,
> +               (void *)&ethdev_mtu_mtu,
> +               (void *)&ethdev_mtu_size,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_prom_mode_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_prom_mode_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_prom_mode_prom =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, prom, "promiscuous");
> +cmdline_parse_token_string_t ethdev_prom_mode_enable =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_prom_mode_cmd_tokens, enable, "on#off");
> +
> +cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx = {
> +       .f = cli_ethdev_prom_mode,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_prom_mode_help,
> +       .tokens = {
> +               (void *)&ethdev_prom_mode_cmd,
> +               (void *)&ethdev_prom_mode_dev,
> +               (void *)&ethdev_prom_mode_prom,
> +               (void *)&ethdev_prom_mode_enable,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_ip4_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_ip4_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_ip4_ip4 =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip4, "ip4");
> +cmdline_parse_token_string_t ethdev_ip4_addr =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, addr, "addr");
> +cmdline_parse_token_string_t ethdev_ip4_add =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t ethdev_ip4_ip =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t ethdev_ip4_netmask =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, netmask, "netmask");
> +cmdline_parse_token_string_t ethdev_ip4_mask =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip4_cmd_tokens, mask, NULL);
> +
> +cmdline_parse_inst_t ethdev_ip4_cmd_ctx = {
> +       .f = cli_ip4_addr,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_ip4_addr_help,
> +       .tokens = {
> +               (void *)&ethdev_ip4_cmd,
> +               (void *)&ethdev_ip4_dev,
> +               (void *)&ethdev_ip4_ip4,
> +               (void *)&ethdev_ip4_addr,
> +               (void *)&ethdev_ip4_add,
> +               (void *)&ethdev_ip4_ip,
> +               (void *)&ethdev_ip4_netmask,
> +               (void *)&ethdev_ip4_mask,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_ip6_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_ip6_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_ip6_ip6 =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip6, "ip6");
> +cmdline_parse_token_string_t ethdev_ip6_addr =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, addr, "addr");
> +cmdline_parse_token_string_t ethdev_ip6_add =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t ethdev_ip6_ip =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t ethdev_ip6_netmask =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, netmask, "netmask");
> +cmdline_parse_token_string_t ethdev_ip6_mask =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_ip6_cmd_tokens, mask, NULL);
> +
> +cmdline_parse_inst_t ethdev_ip6_cmd_ctx = {
> +       .f = cli_ip6_addr,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_ip6_addr_help,
> +       .tokens = {
> +               (void *)&ethdev_ip6_cmd,
> +               (void *)&ethdev_ip6_dev,
> +               (void *)&ethdev_ip6_ip6,
> +               (void *)&ethdev_ip6_addr,
> +               (void *)&ethdev_ip6_add,
> +               (void *)&ethdev_ip6_ip,
> +               (void *)&ethdev_ip6_netmask,
> +               (void *)&ethdev_ip6_mask,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, cmd, "ethdev");
> +cmdline_parse_token_string_t ethdev_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_rxq =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, rxq, "rxq");
> +cmdline_parse_token_num_t ethdev_nb_rxq =
> +       TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_rxq, RTE_UINT16);
> +cmdline_parse_token_string_t ethdev_txq =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, txq, "txq");
> +cmdline_parse_token_num_t ethdev_nb_txq =
> +       TOKEN_NUM_INITIALIZER(struct ethdev_cmd_tokens, nb_txq, RTE_UINT16);
> +cmdline_parse_token_string_t ethdev_mempool =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_cmd_tokens, mempool, NULL);
> +
> +cmdline_parse_inst_t ethdev_cmd_ctx = {
> +       .f = cli_ethdev,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_help,
> +       .tokens = {
> +               (void *)&ethdev_cmd,
> +               (void *)&ethdev_dev,
> +               (void *)&ethdev_rxq,
> +               (void *)&ethdev_nb_rxq,
> +               (void *)&ethdev_txq,
> +               (void *)&ethdev_nb_txq,
> +               (void *)&ethdev_mempool,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, help, "help");
> +cmdline_parse_token_string_t ethdev_help_ethdev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_help_cmd_tokens, ethdev, "ethdev");
> +
> +cmdline_parse_inst_t ethdev_help_cmd_ctx = {
> +       .f = cli_ethdev_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&ethdev_help_cmd,
> +               (void *)&ethdev_help_ethdev,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
> new file mode 100644
> index 0000000000..94d3247a2c
> --- /dev/null
> +++ b/app/graph/ethdev.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_H
> +#define APP_GRAPH_ETHDEV_H
> +
> +#include <cmdline_parse.h>
> +
> +#define ETHDEV_IPV6_ADDR_LEN   16
> +
> +extern cmdline_parse_inst_t ethdev_show_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_stats_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_mtu_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_prom_mode_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_ip4_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_ip6_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_help_cmd_ctx;
> +
> +struct ipv4_addr_config {
> +       uint32_t ip;
> +       uint32_t mask;
> +};
> +
> +struct ipv6_addr_config {
> +       uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +       uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
> +};
> +
> +extern uint32_t enabled_port_mask;
> +
> +void ethdev_start(void);
> +void ethdev_stop(void);
> +void *ethdev_mempool_list_by_portid(uint16_t portid);
> +int16_t ethdev_portid_by_ip4(uint32_t ip, uint32_t mask);
> +int16_t ethdev_portid_by_ip6(uint8_t *ip, uint8_t *mask);
> +void ethdev_list_clean(void);
> +
> +#endif
> diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
> new file mode 100644
> index 0000000000..f231f3f3e1
> --- /dev/null
> +++ b/app/graph/ethdev_priv.h
> @@ -0,0 +1,112 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_PRIV_H
> +#define APP_GRAPH_ETHDEV_PRIV_H
> +
> +#include "ethdev.h"
> +
> +#define NS_PER_SEC 1E9
> +
> +#define ETHDEV_RXQ_RSS_MAX     16
> +#define ETHDEV_RX_DESC_DEFAULT 1024
> +#define ETHDEV_TX_DESC_DEFAULT 1024
> +
> +struct ethdev_show_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t show;
> +};
> +
> +struct ethdev_stats_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t stats;
> +};
> +
> +struct ethdev_mtu_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t mtu;
> +       uint16_t size;
> +};
> +
> +struct ethdev_prom_mode_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t prom;
> +       cmdline_fixed_string_t enable;
> +};
> +
> +struct ethdev_ip4_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t ip4;
> +       cmdline_fixed_string_t addr;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t netmask;
> +       cmdline_fixed_string_t mask;
> +};
> +
> +struct ethdev_ip6_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t ip6;
> +       cmdline_fixed_string_t addr;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t netmask;
> +       cmdline_fixed_string_t mask;
> +};
> +
> +struct ethdev_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t rxq;
> +       cmdline_fixed_string_t txq;
> +       cmdline_fixed_string_t mempool;
> +       uint16_t nb_rxq;
> +       uint16_t nb_txq;
> +};
> +
> +struct ethdev_help_cmd_tokens {
> +       cmdline_fixed_string_t help;
> +       cmdline_fixed_string_t ethdev;
> +};
> +
> +struct ethdev_rss_config {
> +       uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
> +       uint32_t n_queues;
> +};
> +
> +struct ethdev_config {
> +       char dev_name[RTE_ETH_NAME_MAX_LEN];
> +       uint16_t port_id;
> +
> +       struct {
> +               uint32_t n_queues;
> +               uint32_t queue_size;
> +               char mempool_name[RTE_MEMPOOL_NAMESIZE];
> +               struct rte_mempool *mp;
> +               struct ethdev_rss_config *rss;
> +       } rx;
> +
> +       struct {
> +               uint32_t n_queues;
> +               uint32_t queue_size;
> +       } tx;
> +
> +       int promiscuous;
> +       uint32_t mtu;
> +};
> +
> +struct ethdev {
> +       TAILQ_ENTRY(ethdev) next;
> +       struct ethdev_config config;
> +       struct ipv4_addr_config ip4_addr;
> +       struct ipv6_addr_config ip6_addr;
> +};
> +TAILQ_HEAD(ethdev_head, ethdev);
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> index 96548f49e7..c1cb435588 100644
> --- a/app/graph/main.c
> +++ b/app/graph/main.c
> @@ -215,6 +215,7 @@ main(int argc, char **argv)
>
>  exit:
>         conn_free(conn);
> +       ethdev_stop();
>         cli_exit();
>         rte_eal_cleanup();
>         return 0;
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index 5dc23c875b..c17e0cc63e 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
>  sources = files(
>          'cli.c',
>          'conn.c',
> +        'ethdev.c',
>          'main.c',
>          'mempool.c',
>          'utils.c',
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index b45419811b..e8a6ccb562 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -10,6 +10,7 @@
>
>  #include "cli.h"
>  #include "conn.h"
> +#include "ethdev.h"
>  #include "mempool.h"
>  #include "utils.h"
>  /*
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 6009b0c291..5dedea97de 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -78,7 +78,39 @@ file to express the requested use case configuration.
>     +--------------------------------------+-----------------------------------+---------+----------+
>     | help mempool                         | | Command to dump mempool help    |   Yes   |    Yes   |
>     |                                      | | message.                        |         |          |
> -   |                                      |                                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ethdev <ethdev_name> rxq <n_queues>| | Command to create DPDK port with|   No    |    No    |
> +   | | txq <n_queues> <mempool_name>      | | given number of Rx and Tx queues|         |          |
> +   |                                      | | . Also attach RxQ with given    |         |          |
> +   |                                      | | mempool. Each port can have     |         |          |
> +   |                                      | | single mempool only i.e. all    |         |          |
> +   |                                      | | RxQs will share the same mempool|         |          |
> +   |                                      | | .                               |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | ethdev <ethdev_name> mtu <mtu_sz>    | | Command to configure MTU of DPDK|   Yes   |    Yes   |
> +   |                                      | | port.                           |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   |  | ethdev <ethdev_name> promiscuous  | | Command to enable/disable       |   Yes   |    Yes   |
> +   |  | <on/off>                          | | promiscuous mode on DPDK port.  |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | ethdev <ethdev_name> show            | | Command to dump current ethdev  |   Yes   |    Yes   |
> +   |                                      | | configuration.                  |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | ethdev <ethdev_name> stats           | | Command to dump current ethdev  |   Yes   |    Yes   |
> +   |                                      | | statistics.                     |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ethdev <ethdev_name> ip4 addr add  | | Command to configure IPv4       |   Yes   |    Yes   |
> +   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
> +   |                                      | | is needed if user wishes to use |         |          |
> +   |                                      | | ``ipv4_lookup`` node.           |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ethdev <ethdev_name> ip6 addr add  | | Command to configure IPv6       |   Yes   |    Yes   |
> +   | | <ip> netmask <mask>                | | address on given PCI device. It |         |          |
> +   |                                      | | is needed if user wishes to use |         |          |
> +   |                                      | | ``ipv6_lookup`` node.           |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
> +   |                                      | | message.                        |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
>
>  Runtime configuration
> @@ -113,6 +145,22 @@ Example: ``dpdk-graph`` is started with -h 10.28.35.207 and -p 50000 then
>
>     graph>
>     graph>
> +   graph> help ethdev
> +
> +   ----------------------------- ethdev command help -----------------------------
> +   ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name>
> +   ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> +   ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> +   ethdev <ethdev_name> promiscuous <on/off>
> +   ethdev <ethdev_name> mtu <mtu_sz>
> +   ethdev <ethdev_name> stats
> +   ethdev <ethdev_name> show
> +   graph>
> +
> +To exit the telnet session, type ``Ctrl + ]``. This changes the ``graph>`` command prompt to
> +``telnet>`` command prompt. Now running ``close`` or ``quit`` command on ``telnet>`` prompt
> +will terminate the telnet session.
> +
>  Created graph for use case
>  --------------------------
>
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 06/12] app/graph: support IPv4 lookup command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 06/12] app/graph: support IPv4 lookup " skori
@ 2023-10-23  7:04                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:04 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 8:07 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds ipv4_lookup module to configure LPM table. This LPM table
> will be used for IPv4 lookup and forwarding.
>
> Following commands are exposed:
>  - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
>  - help ipv4_lookup
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   2 +
>  app/graph/ethdev.c         |   2 +-
>  app/graph/ip4_route.c      | 221 +++++++++++++++++++++++++++++++++++++
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   1 +
>  app/graph/route.h          |  26 +++++
>  app/graph/route_priv.h     |  44 ++++++++
>  doc/guides/tools/graph.rst |   9 ++
>  8 files changed, 305 insertions(+), 1 deletion(-)
>  create mode 100644 app/graph/ip4_route.c
>  create mode 100644 app/graph/route.h
>  create mode 100644 app/graph/route_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index fa394fade6..25785ea4dc 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> index 8df55b4b12..4e4d23b692 100644
> --- a/app/graph/ethdev.c
> +++ b/app/graph/ethdev.c
> @@ -164,7 +164,7 @@ ethdev_stop(void)
>         }
>
>         ethdev_list_clean();
> -       rte_eal_cleanup();
> +       route_ip4_list_clean();
>         printf("Bye...\n");
>  }
>
> diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
> new file mode 100644
> index 0000000000..db3354c270
> --- /dev/null
> +++ b/app/graph/ip4_route.c
> @@ -0,0 +1,221 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_node_ip4_api.h>
> +
> +#include "module_api.h"
> +#include "route_priv.h"
> +
> +static const char
> +cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
> +
> +struct ip4_route route4 = TAILQ_HEAD_INITIALIZER(route4);
> +
> +
> +void
> +route_ip4_list_clean(void)
> +{
> +       struct route_ipv4_config *route;
> +
> +       while (!TAILQ_EMPTY(&route4)) {
> +               route = TAILQ_FIRST(&route4);
> +               TAILQ_REMOVE(&route4, route, next);
> +       }
> +}
> +
> +static struct route_ipv4_config *
> +find_route4_entry(struct route_ipv4_config *route)
> +{
> +       struct route_ipv4_config *ipv4route;
> +
> +       TAILQ_FOREACH(ipv4route, &route4, next) {
> +               if (!memcmp(ipv4route, route, sizeof(*route)))
> +                       return ipv4route;
> +       }
> +       return NULL;
> +
> +}
> +
> +static uint8_t
> +convert_netmask_to_depth(uint32_t netmask)
> +{
> +       uint8_t zerobits = 0;
> +
> +       while ((netmask & 0x1) == 0) {
> +               netmask = netmask >> 1;
> +               zerobits++;
> +       }
> +
> +       return (32 - zerobits);
> +}
> +
> +static int
> +route4_rewirte_table_update(struct route_ipv4_config *ipv4route)
> +{
> +       uint8_t depth;
> +       int portid;
> +
> +       portid = ethdev_portid_by_ip4(ipv4route->via, ipv4route->netmask);
> +       if (portid < 0) {
> +               printf("Invalid portid found to install the route\n");
> +               return portid;
> +       }
> +
> +       depth = convert_netmask_to_depth(ipv4route->netmask);
> +
> +       return rte_node_ip4_route_add(ipv4route->ip, depth, portid,
> +                       RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
> +}
> +
> +static int
> +route_ip4_add(struct route_ipv4_config *route)
> +{
> +       struct route_ipv4_config *ipv4route;
> +       int rc = -EINVAL;
> +
> +       ipv4route = find_route4_entry(route);
> +
> +       if (!ipv4route) {
> +               ipv4route = malloc(sizeof(struct route_ipv4_config));
> +               if (!ipv4route)
> +                       return -ENOMEM;
> +       } else {
> +               return 0;
> +       }
> +
> +       ipv4route->ip = route->ip;
> +       ipv4route->netmask = route->netmask;
> +       ipv4route->via = route->via;
> +       ipv4route->is_used = true;
> +
> +       /* FIXME: Get graph status here and then update table */
> +       rc = route4_rewirte_table_update(ipv4route);
> +       if (rc)
> +               goto free;
> +
> +       TAILQ_INSERT_TAIL(&route4, ipv4route, next);
> +       return 0;
> +free:
> +       free(ipv4route);
> +       return rc;
> +}
> +
> +int
> +route_ip4_add_to_lookup(void)
> +{
> +       struct route_ipv4_config *route = NULL;
> +       int rc = -EINVAL;
> +
> +       TAILQ_FOREACH(route, &route4, next) {
> +               rc = route4_rewirte_table_update(route);
> +               if (rc < 0)
> +                       return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +static void
> +cli_ipv4_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                    __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
> +                "--------------------------- ipv4_lookup command help ---------------------------",
> +                cmd_ipv4_lookup_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +static void
> +cli_ipv4_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ip4_lookup_cmd_tokens *res = parsed_result;
> +       struct route_ipv4_config config;
> +       int rc = -EINVAL;
> +
> +       if (parser_ip4_read(&config.ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ipv4");
> +               return;
> +       }
> +
> +       if (parser_ip4_read(&config.netmask, res->mask)) {
> +               printf(MSG_ARG_INVALID, "netmask");
> +               return;
> +       }
> +
> +       if (parser_ip4_read(&config.via, res->via_ip)) {
> +               printf(MSG_ARG_INVALID, "via ip");
> +               return;
> +       }
> +
> +       rc = route_ip4_add(&config);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +cmdline_parse_token_string_t ip4_lookup_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, cmd, "ipv4_lookup");
> +cmdline_parse_token_string_t ip4_lookup_route =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, route, "route");
> +cmdline_parse_token_string_t ip4_lookup_add =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t ip4_lookup_ip4 =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip4, "ipv4");
> +cmdline_parse_token_string_t ip4_lookup_ip =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t ip4_lookup_netmask =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, netmask, "netmask");
> +cmdline_parse_token_string_t ip4_lookup_mask =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, mask, NULL);
> +cmdline_parse_token_string_t ip4_lookup_via =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via, "via");
> +cmdline_parse_token_string_t ip4_lookup_via_ip =
> +       TOKEN_STRING_INITIALIZER(struct ip4_lookup_cmd_tokens, via_ip, NULL);
> +
> +cmdline_parse_inst_t ipv4_lookup_cmd_ctx = {
> +       .f = cli_ipv4_lookup,
> +       .data = NULL,
> +       .help_str = cmd_ipv4_lookup_help,
> +       .tokens = {
> +               (void *)&ip4_lookup_cmd,
> +               (void *)&ip4_lookup_route,
> +               (void *)&ip4_lookup_add,
> +               (void *)&ip4_lookup_ip4,
> +               (void *)&ip4_lookup_ip,
> +               (void *)&ip4_lookup_netmask,
> +               (void *)&ip4_lookup_mask,
> +               (void *)&ip4_lookup_via,
> +               (void *)&ip4_lookup_via_ip,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ipv4_lookup_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, cmd, "help");
> +cmdline_parse_token_string_t ipv4_lookup_help_module =
> +       TOKEN_STRING_INITIALIZER(struct ipv4_lookup_help_cmd_tokens, module, "ipv4_lookup");
> +
> +cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx = {
> +       .f = cli_ipv4_lookup_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&ipv4_lookup_help_cmd,
> +               (void *)&ipv4_lookup_help_module,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index c17e0cc63e..1f35f82583 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -13,6 +13,7 @@ sources = files(
>          'cli.c',
>          'conn.c',
>          'ethdev.c',
> +        'ip4_route.c',
>          'main.c',
>          'mempool.c',
>          'utils.c',
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index e8a6ccb562..bd4d245c75 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -12,6 +12,7 @@
>  #include "conn.h"
>  #include "ethdev.h"
>  #include "mempool.h"
> +#include "route.h"
>  #include "utils.h"
>  /*
>   * Externs
> diff --git a/app/graph/route.h b/app/graph/route.h
> new file mode 100644
> index 0000000000..a44d401d55
> --- /dev/null
> +++ b/app/graph/route.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ROUTE_H
> +#define APP_GRAPH_ROUTE_H
> +
> +#define MAX_ROUTE_ENTRIES 32
> +
> +extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
> +extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
> +
> +struct route_ipv4_config {
> +       TAILQ_ENTRY(route_ipv4_config) next;
> +       uint32_t ip;
> +       uint32_t netmask;
> +       uint32_t via;
> +       bool is_used;
> +};
> +
> +TAILQ_HEAD(ip4_route, route_ipv4_config);
> +
> +int route_ip4_add_to_lookup(void);
> +void route_ip4_list_clean(void);
> +
> +#endif
> diff --git a/app/graph/route_priv.h b/app/graph/route_priv.h
> new file mode 100644
> index 0000000000..f363a551a9
> --- /dev/null
> +++ b/app/graph/route_priv.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ROUTE_PRIV_H
> +#define APP_GRAPH_ROUTE_PRIV_H
> +
> +#define MAX_ROUTE_ENTRIES 32
> +
> +struct ip4_lookup_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t route;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip4;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t netmask;
> +       cmdline_fixed_string_t mask;
> +       cmdline_fixed_string_t via;
> +       cmdline_fixed_string_t via_ip;
> +};
> +
> +struct ip6_lookup_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t route;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip6;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t netmask;
> +       cmdline_fixed_string_t mask;
> +       cmdline_fixed_string_t via;
> +       cmdline_fixed_string_t via_ip;
> +};
> +
> +struct ipv4_lookup_help_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t module;
> +};
> +
> +struct ipv6_lookup_help_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t module;
> +};
> +
> +#endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 5dedea97de..7530ef6f65 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -112,6 +112,15 @@ file to express the requested use case configuration.
>     | help ethdev                          | | Command to dump ethdev help     |   Yes   |    Yes   |
>     |                                      | | message.                        |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ipv4_lookup route add ipv4 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
> +   | |  netmask <mask> via <ip>           | | ``ipv4_lookup`` LPM table. It is|         |          |
> +   |                                      | | needed if user wishes to route  |         |          |
> +   |                                      | | the packets based on LPM lookup |         |          |
> +   |                                      | | table.                          |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
> +   |                                      | | help message.                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
>
>  Runtime configuration
>  ---------------------
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 07/12] app/graph: support IPv6 lookup command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 07/12] app/graph: support IPv6 " skori
@ 2023-10-23  7:04                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:04 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 2:37 AM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> Adds ipv6_lookup module to configure LPM6 table. This LPM6 table
> will be used for IPv6 lookup and forwarding.
>
> Following commands are exposed:
>  - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
>  - help ipv6_lookup
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   2 +
>  app/graph/ethdev.c         |   1 +
>  app/graph/ip6_route.c      | 226 +++++++++++++++++++++++++++++++++++++
>  app/graph/meson.build      |   1 +
>  app/graph/route.h          |  14 +++
>  doc/guides/tools/graph.rst |   9 ++
>  6 files changed, 253 insertions(+)
>  create mode 100644 app/graph/ip6_route.c
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index 25785ea4dc..1280422388 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -32,6 +32,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> index 4e4d23b692..e3f9ee3e0c 100644
> --- a/app/graph/ethdev.c
> +++ b/app/graph/ethdev.c
> @@ -165,6 +165,7 @@ ethdev_stop(void)
>
>         ethdev_list_clean();
>         route_ip4_list_clean();
> +       route_ip6_list_clean();
>         printf("Bye...\n");
>  }
>
> diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
> new file mode 100644
> index 0000000000..e793cde830
> --- /dev/null
> +++ b/app/graph/ip6_route.c
> @@ -0,0 +1,226 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +
> +#include <rte_node_ip6_api.h>
> +
> +#include "module_api.h"
> +#include "route_priv.h"
> +
> +static const char
> +cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
> +
> +struct ip6_route route6 = TAILQ_HEAD_INITIALIZER(route6);
> +
> +void
> +route_ip6_list_clean(void)
> +{
> +       struct route_ipv6_config *route;
> +
> +       while (!TAILQ_EMPTY(&route6)) {
> +               route = TAILQ_FIRST(&route6);
> +               TAILQ_REMOVE(&route6, route, next);
> +       }
> +}
> +
> +static struct route_ipv6_config *
> +find_route6_entry(struct route_ipv6_config *route)
> +{
> +       struct route_ipv6_config *ipv6route;
> +
> +       TAILQ_FOREACH(ipv6route, &route6, next) {
> +               if (!memcmp(ipv6route, route, sizeof(*route)))
> +                       return ipv6route;
> +       }
> +       return NULL;
> +}
> +
> +static uint8_t
> +convert_ip6_netmask_to_depth(uint8_t *netmask)
> +{
> +       uint8_t setbits = 0;
> +       uint8_t mask;
> +       int i;
> +
> +       for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> +               mask = netmask[i];
> +               while (mask & 0x80) {
> +                       mask = mask << 1;
> +                       setbits++;
> +               }
> +       }
> +
> +       return setbits;
> +}
> +
> +static int
> +route6_rewirte_table_update(struct route_ipv6_config *ipv6route)
> +{
> +       uint8_t depth;
> +       int portid;
> +
> +       portid = ethdev_portid_by_ip6(ipv6route->gateway, ipv6route->mask);
> +       if (portid < 0) {
> +               printf("Invalid portid found to install the route\n");
> +               return portid;
> +       }
> +       depth = convert_ip6_netmask_to_depth(ipv6route->mask);
> +
> +       return rte_node_ip6_route_add(ipv6route->ip, depth, portid,
> +                       RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
> +
> +}
> +
> +static int
> +route_ip6_add(struct route_ipv6_config *route)
> +{
> +       struct route_ipv6_config *ipv6route;
> +       int rc = -EINVAL;
> +       int j;
> +
> +       ipv6route = find_route6_entry(route);
> +       if (!ipv6route) {
> +               ipv6route = malloc(sizeof(struct route_ipv6_config));
> +               if (!ipv6route)
> +                       return -ENOMEM;
> +       } else {
> +               return 0;
> +       }
> +
> +       for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> +               ipv6route->ip[j] = route->ip[j];
> +               ipv6route->mask[j] = route->mask[j];
> +               ipv6route->gateway[j] = route->gateway[j];
> +       }
> +       ipv6route->is_used = true;
> +
> +       /* FIXME: Get graph status here and then update table */
> +       rc = route6_rewirte_table_update(ipv6route);
> +       if (rc)
> +               goto free;
> +
> +       TAILQ_INSERT_TAIL(&route6, ipv6route, next);
> +       return 0;
> +free:
> +       free(ipv6route);
> +       return rc;
> +}
> +
> +int
> +route_ip6_add_to_lookup(void)
> +{
> +       struct route_ipv6_config *route = NULL;
> +       int rc = -EINVAL;
> +
> +       TAILQ_FOREACH(route, &route6, next) {
> +               rc = route6_rewirte_table_update(route);
> +               if (rc < 0)
> +                       return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +static void
> +cli_ipv6_lookup_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                    __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
> +                "--------------------------- ipv6_lookup command help ---------------------------",
> +                cmd_ipv6_lookup_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +static void
> +cli_ipv6_lookup(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ip6_lookup_cmd_tokens *res = parsed_result;
> +       struct route_ipv6_config config;
> +       int rc = -EINVAL;
> +
> +       if (parser_ip6_read(config.ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ipv6");
> +               return;
> +       }
> +
> +       if (parser_ip6_read(config.mask, res->mask)) {
> +               printf(MSG_ARG_INVALID, "netmask");
> +               return;
> +       }
> +
> +       if (parser_ip6_read(config.gateway, res->via_ip)) {
> +               printf(MSG_ARG_INVALID, "gateway ip");
> +               return;
> +       }
> +
> +       rc = route_ip6_add(&config);
> +       if (rc)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +cmdline_parse_token_string_t ip6_lookup_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, cmd, "ipv6_lookup");
> +cmdline_parse_token_string_t ip6_lookup_route =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, route, "route");
> +cmdline_parse_token_string_t ip6_lookup_add =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t ip6_lookup_ip6 =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip6, "ipv6");
> +cmdline_parse_token_string_t ip6_lookup_ip =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t ip6_lookup_netmask =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, netmask, "netmask");
> +cmdline_parse_token_string_t ip6_lookup_mask =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, mask, NULL);
> +cmdline_parse_token_string_t ip6_lookup_via =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via, "via");
> +cmdline_parse_token_string_t ip6_lookup_via_ip =
> +       TOKEN_STRING_INITIALIZER(struct ip6_lookup_cmd_tokens, via_ip, NULL);
> +
> +cmdline_parse_inst_t ipv6_lookup_cmd_ctx = {
> +       .f = cli_ipv6_lookup,
> +       .data = NULL,
> +       .help_str = cmd_ipv6_lookup_help,
> +       .tokens = {
> +               (void *)&ip6_lookup_cmd,
> +               (void *)&ip6_lookup_route,
> +               (void *)&ip6_lookup_add,
> +               (void *)&ip6_lookup_ip6,
> +               (void *)&ip6_lookup_ip,
> +               (void *)&ip6_lookup_netmask,
> +               (void *)&ip6_lookup_mask,
> +               (void *)&ip6_lookup_via,
> +               (void *)&ip6_lookup_via_ip,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ipv6_lookup_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, cmd, "help");
> +cmdline_parse_token_string_t ipv6_lookup_help_module =
> +       TOKEN_STRING_INITIALIZER(struct ipv6_lookup_help_cmd_tokens, module, "ipv6_lookup");
> +
> +cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx = {
> +       .f = cli_ipv6_lookup_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&ipv6_lookup_help_cmd,
> +               (void *)&ipv6_lookup_help_module,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index 1f35f82583..413bbefc4e 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -14,6 +14,7 @@ sources = files(
>          'conn.c',
>          'ethdev.c',
>          'ip4_route.c',
> +        'ip6_route.c',
>          'main.c',
>          'mempool.c',
>          'utils.c',
> diff --git a/app/graph/route.h b/app/graph/route.h
> index a44d401d55..0d271d1350 100644
> --- a/app/graph/route.h
> +++ b/app/graph/route.h
> @@ -8,7 +8,9 @@
>  #define MAX_ROUTE_ENTRIES 32
>
>  extern cmdline_parse_inst_t ipv4_lookup_cmd_ctx;
> +extern cmdline_parse_inst_t ipv6_lookup_cmd_ctx;
>  extern cmdline_parse_inst_t ipv4_lookup_help_cmd_ctx;
> +extern cmdline_parse_inst_t ipv6_lookup_help_cmd_ctx;
>
>  struct route_ipv4_config {
>         TAILQ_ENTRY(route_ipv4_config) next;
> @@ -20,7 +22,19 @@ struct route_ipv4_config {
>
>  TAILQ_HEAD(ip4_route, route_ipv4_config);
>
> +struct route_ipv6_config {
> +       TAILQ_ENTRY(route_ipv6_config) next;
> +       uint8_t ip[16];
> +       uint8_t mask[16];
> +       uint8_t gateway[16];
> +       bool is_used;
> +};
> +
> +TAILQ_HEAD(ip6_route, route_ipv6_config);
> +
>  int route_ip4_add_to_lookup(void);
> +int route_ip6_add_to_lookup(void);
>  void route_ip4_list_clean(void);
> +void route_ip6_list_clean(void);
>
>  #endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 7530ef6f65..56c2eaad26 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -121,6 +121,15 @@ file to express the requested use case configuration.
>     | help ipv4_lookup                     | | Command to dump ``ipv4_lookup`` |   Yes   |    Yes   |
>     |                                      | | help message.                   |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ipv6_lookup route add ipv6 <ip>    | | Command to add a route into     |   Yes   |    Yes   |
> +   | |  netmask <mask> via <ip>           | | ``ipv6_lookup`` LPM table. It is|         |          |
> +   |                                      | | needed if user wishes to route  |         |          |
> +   |                                      | | the packets based on LPM6 lookup|         |          |
> +   |                                      | | table.                          |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
> +   |                                      | | help message.                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
>
>  Runtime configuration
>  ---------------------
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 08/12] app/graph: support neigh command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 08/12] app/graph: support neigh " skori
@ 2023-10-23  7:05                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:05 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Thu, Oct 19, 2023 at 11:17 PM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds neigh module to configure arp/neigh. This module uses
> ipv4_rewrite and ipv6_rewrite node to write neigh information.
>
> Following commands are exposed:
>  - neigh add ipv4 <ip> <mac>
>  - neigh add ipv6 <ip> <mac>
>  - help neigh
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   3 +
>  app/graph/ethdev.c         |   2 +
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   2 +
>  app/graph/neigh.c          | 358 +++++++++++++++++++++++++++++++++++++
>  app/graph/neigh.h          |  17 ++
>  app/graph/neigh_priv.h     |  49 +++++
>  doc/guides/tools/graph.rst |  11 ++
>  8 files changed, 443 insertions(+)
>  create mode 100644 app/graph/neigh.c
>  create mode 100644 app/graph/neigh.h
>  create mode 100644 app/graph/neigh_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index 1280422388..f564362da1 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -34,6 +34,9 @@ cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv6_lookup_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&neigh_v4_cmd_ctx,
> +       (cmdline_parse_inst_t *)&neigh_v6_cmd_ctx,
> +       (cmdline_parse_inst_t *)&neigh_help_cmd_ctx,
>         NULL,
>  };
>
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> index e3f9ee3e0c..c9b09168c1 100644
> --- a/app/graph/ethdev.c
> +++ b/app/graph/ethdev.c
> @@ -166,6 +166,8 @@ ethdev_stop(void)
>         ethdev_list_clean();
>         route_ip4_list_clean();
>         route_ip6_list_clean();
> +       neigh4_list_clean();
> +       neigh6_list_clean();
>         printf("Bye...\n");
>  }
>
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index 413bbefc4e..8fa9d605b9 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -17,5 +17,6 @@ sources = files(
>          'ip6_route.c',
>          'main.c',
>          'mempool.c',
> +        'neigh.c',
>          'utils.c',
>  )
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index bd4d245c75..e9e42da7cc 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -12,8 +12,10 @@
>  #include "conn.h"
>  #include "ethdev.h"
>  #include "mempool.h"
> +#include "neigh.h"
>  #include "route.h"
>  #include "utils.h"
> +
>  /*
>   * Externs
>   */
> diff --git a/app/graph/neigh.c b/app/graph/neigh.c
> new file mode 100644
> index 0000000000..0cee502719
> --- /dev/null
> +++ b/app/graph/neigh.c
> @@ -0,0 +1,358 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_ethdev.h>
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "neigh_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
> +
> +static const char
> +cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
> +
> +struct neigh4_head neigh4 = TAILQ_HEAD_INITIALIZER(neigh4);
> +struct neigh6_head neigh6 = TAILQ_HEAD_INITIALIZER(neigh6);
> +
> +void
> +neigh4_list_clean(void)
> +{
> +       struct neigh_ipv4_config *v4_config;
> +
> +       while (!TAILQ_EMPTY(&neigh4)) {
> +               v4_config = TAILQ_FIRST(&neigh4);
> +               TAILQ_REMOVE(&neigh4, v4_config, next);
> +       }
> +}
> +
> +void
> +neigh6_list_clean(void)
> +{
> +       struct neigh_ipv6_config *v6_config;
> +
> +       while (!TAILQ_EMPTY(&neigh6)) {
> +               v6_config = TAILQ_FIRST(&neigh6);
> +               TAILQ_REMOVE(&neigh6, v6_config, next);
> +       }
> +}
> +
> +static struct neigh_ipv4_config *
> +find_neigh4_entry(uint32_t ip, uint64_t mac)
> +{
> +       struct neigh_ipv4_config *v4_config;
> +
> +       TAILQ_FOREACH(v4_config, &neigh4, next) {
> +               if ((v4_config->ip == ip) && (v4_config->mac == mac))
> +                       return v4_config;
> +       }
> +       return NULL;
> +}
> +
> +static struct neigh_ipv6_config *
> +find_neigh6_entry(uint8_t *ip, uint64_t mac)
> +{
> +       struct neigh_ipv6_config *v6_config;
> +
> +       TAILQ_FOREACH(v6_config, &neigh6, next) {
> +               if (!(memcmp(v6_config->ip, ip, 16)) && (v6_config->mac == mac))
> +                       return v6_config;
> +       }
> +       return NULL;
> +}
> +
> +static int
> +ip6_rewrite_node_add(struct neigh_ipv6_config *v6_config)
> +{
> +       uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +       uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +       struct rte_ether_addr smac;
> +       int16_t portid = 0;
> +       int rc;
> +
> +       portid = ethdev_portid_by_ip6(v6_config->ip, NULL);
> +       if (portid < 0) {
> +               printf("Invalid portid found to add neigh\n");
> +               return -EINVAL;
> +       }
> +
> +       memset(data, 0, len);
> +
> +       /* Copy dst mac */
> +       rte_memcpy((void *)&data[0], (void *)&v6_config->mac, RTE_ETHER_ADDR_LEN);
> +
> +       /* Copy src mac */
> +       rc = rte_eth_macaddr_get(portid, &smac);
> +       if (rc < 0)
> +               return rc;
> +
> +       rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
> +
> +       return rte_node_ip6_rewrite_add(portid, data, len, portid);
> +}
> +
> +static int
> +ip4_rewrite_node_add(struct neigh_ipv4_config *v4_config)
> +{
> +       uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> +       uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> +       struct rte_ether_addr smac;
> +       int16_t portid = 0;
> +       int rc;
> +
> +       portid = ethdev_portid_by_ip4(v4_config->ip, 0);
> +       if (portid < 0) {
> +               printf("Invalid portid found to add  neigh\n");
> +               return -EINVAL;
> +       }
> +
> +       memset(data, 0, len);
> +
> +       /* Copy dst mac */
> +       rte_memcpy((void *)&data[0], (void *)&v4_config->mac, RTE_ETHER_ADDR_LEN);
> +
> +       /* Copy src mac */
> +       rc = rte_eth_macaddr_get(portid, &smac);
> +       if (rc < 0) {
> +               printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
> +               return rc;
> +       }
> +
> +       rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes, RTE_ETHER_ADDR_LEN);
> +
> +       return rte_node_ip4_rewrite_add(portid, data, len, portid);
> +}
> +
> +
> +static int
> +neigh_ip4_add(uint32_t ip, uint64_t mac)
> +{
> +       struct neigh_ipv4_config *v4_config;
> +       int rc = -EINVAL;
> +
> +       v4_config = find_neigh4_entry(ip, mac);
> +
> +       if (!v4_config) {
> +               v4_config = malloc(sizeof(struct neigh_ipv4_config));
> +               if (!v4_config)
> +                       return -ENOMEM;
> +       }
> +
> +       v4_config->ip = ip;
> +       v4_config->mac = mac;
> +       v4_config->is_used = true;
> +
> +       /* FIXME: Get graph status here and then update table */
> +       rc = ip4_rewrite_node_add(v4_config);
> +       if (rc)
> +               goto free;
> +
> +       TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
> +       return 0;
> +free:
> +       free(v4_config);
> +       return rc;
> +}
> +
> +static int
> +neigh_ip6_add(uint8_t *ip, uint64_t mac)
> +{
> +       struct neigh_ipv6_config *v6_config;
> +       int rc = -EINVAL;
> +       int j;
> +
> +       v6_config = find_neigh6_entry(ip, mac);
> +
> +       if (!v6_config) {
> +               v6_config = malloc(sizeof(struct neigh_ipv6_config));
> +               if (!v6_config)
> +                       return -ENOMEM;
> +       }
> +
> +       for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
> +               v6_config->ip[j] = ip[j];
> +
> +       v6_config->mac = mac;
> +       v6_config->is_used = true;
> +
> +       /* FIXME: Get graph status here and then update table */
> +       rc =  ip6_rewrite_node_add(v6_config);
> +       if (rc)
> +               goto free;
> +
> +       TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
> +       return 0;
> +free:
> +       free(v6_config);
> +       return rc;
> +}
> +
> +int
> +neigh_ip4_add_to_rewrite(void)
> +{
> +       struct neigh_ipv4_config *neigh;
> +       int rc;
> +
> +       TAILQ_FOREACH(neigh, &neigh4, next) {
> +               rc = ip4_rewrite_node_add(neigh);
> +               if (rc)
> +                       return rc;
> +       }
> +       return 0;
> +}
> +
> +int
> +neigh_ip6_add_to_rewrite(void)
> +{
> +       struct neigh_ipv6_config *neigh;
> +       int rc;
> +
> +
> +       TAILQ_FOREACH(neigh, &neigh6, next) {
> +               rc = ip6_rewrite_node_add(neigh);
> +               if (rc < 0)
> +                       return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +static void
> +cli_neigh_v4(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct neigh_v4_cmd_tokens *res = parsed_result;
> +       int rc = -EINVAL;
> +       uint64_t mac;
> +       uint32_t ip;
> +
> +       if (parser_ip4_read(&ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ip");
> +               return;
> +       }
> +
> +       if (parser_mac_read(&mac, res->mac)) {
> +               printf(MSG_ARG_INVALID, "mac");
> +               return;
> +       }
> +
> +       rc = neigh_ip4_add(ip, mac);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_neigh_v6(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct neigh_v6_cmd_tokens *res = parsed_result;
> +       uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> +       int rc = -EINVAL;
> +       uint64_t mac;
> +
> +       if (parser_ip6_read(ip, res->ip)) {
> +               printf(MSG_ARG_INVALID, "ip");
> +               return;
> +       }
> +
> +       if (parser_mac_read(&mac, res->mac)) {
> +               printf(MSG_ARG_INVALID, "mac");
> +               return;
> +       }
> +
> +       rc = neigh_ip6_add(ip, mac);
> +       if (rc < 0)
> +               printf(MSG_CMD_FAIL, res->cmd);
> +}
> +
> +static void
> +cli_neigh_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +              __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n",
> +                "--------------------------- neigh command help ---------------------------",
> +                cmd_neigh_v4_help, cmd_neigh_v6_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +cmdline_parse_token_string_t neigh_v4_cmd =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, cmd, "neigh");
> +cmdline_parse_token_string_t neigh_v4_add =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t neigh_v4_ip4 =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip4, "ipv4");
> +cmdline_parse_token_string_t neigh_v4_ip =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t neigh_v4_mac =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v4_cmd_tokens, mac, NULL);
> +
> +cmdline_parse_inst_t neigh_v4_cmd_ctx = {
> +       .f = cli_neigh_v4,
> +       .data = NULL,
> +       .help_str = cmd_neigh_v4_help,
> +       .tokens = {
> +               (void *)&neigh_v4_cmd,
> +               (void *)&neigh_v4_add,
> +               (void *)&neigh_v4_ip4,
> +               (void *)&neigh_v4_ip,
> +               (void *)&neigh_v4_mac,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t neigh_v6_cmd =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, cmd, "neigh");
> +cmdline_parse_token_string_t neigh_v6_add =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, add, "add");
> +cmdline_parse_token_string_t neigh_v6_ip6 =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip6, "ipv6");
> +cmdline_parse_token_string_t neigh_v6_ip =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, ip, NULL);
> +cmdline_parse_token_string_t neigh_v6_mac =
> +       TOKEN_STRING_INITIALIZER(struct neigh_v6_cmd_tokens, mac, NULL);
> +
> +cmdline_parse_inst_t neigh_v6_cmd_ctx = {
> +       .f = cli_neigh_v6,
> +       .data = NULL,
> +       .help_str = cmd_neigh_v6_help,
> +       .tokens = {
> +               (void *)&neigh_v6_cmd,
> +               (void *)&neigh_v6_add,
> +               (void *)&neigh_v6_ip6,
> +               (void *)&neigh_v6_ip,
> +               (void *)&neigh_v6_mac,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t neigh_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, cmd, "help");
> +cmdline_parse_token_string_t neigh_help_module =
> +       TOKEN_STRING_INITIALIZER(struct neigh_help_cmd_tokens, module, "neigh");
> +
> +cmdline_parse_inst_t neigh_help_cmd_ctx = {
> +       .f = cli_neigh_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&neigh_help_cmd,
> +               (void *)&neigh_help_module,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/neigh.h b/app/graph/neigh.h
> new file mode 100644
> index 0000000000..928981fc31
> --- /dev/null
> +++ b/app/graph/neigh.h
> @@ -0,0 +1,17 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_H
> +#define APP_GRAPH_NEIGH_H
> +
> +extern cmdline_parse_inst_t neigh_v4_cmd_ctx;
> +extern cmdline_parse_inst_t neigh_v6_cmd_ctx;
> +extern cmdline_parse_inst_t neigh_help_cmd_ctx;
> +
> +void neigh4_list_clean(void);
> +void neigh6_list_clean(void);
> +int neigh_ip4_add_to_rewrite(void);
> +int neigh_ip6_add_to_rewrite(void);
> +
> +#endif
> diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
> new file mode 100644
> index 0000000000..0ec9b1510f
> --- /dev/null
> +++ b/app/graph/neigh_priv.h
> @@ -0,0 +1,49 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_PRIV_H
> +#define APP_GRAPH_NEIGH_PRIV_H
> +
> +#define MAX_NEIGH_ENTRIES 32
> +
> +struct neigh_v4_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip4;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t mac;
> +};
> +
> +struct neigh_v6_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t add;
> +       cmdline_fixed_string_t ip6;
> +       cmdline_fixed_string_t ip;
> +       cmdline_fixed_string_t mac;
> +};
> +
> +struct neigh_help_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t module;
> +};
> +
> +struct neigh_ipv4_config {
> +       TAILQ_ENTRY(neigh_ipv4_config) next;
> +       uint32_t ip;
> +       uint64_t mac;
> +       bool is_used;
> +};
> +
> +TAILQ_HEAD(neigh4_head, neigh_ipv4_config);
> +
> +struct neigh_ipv6_config {
> +       TAILQ_ENTRY(neigh_ipv6_config) next;
> +       uint8_t ip[16];
> +       uint64_t mac;
> +       bool is_used;
> +};
> +
> +TAILQ_HEAD(neigh6_head, neigh_ipv6_config);
> +
> +#endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 56c2eaad26..b1bd2e6048 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -130,6 +130,17 @@ file to express the requested use case configuration.
>     | help ipv6_lookup                     | | Command to dump ``ipv6_lookup`` |   Yes   |    Yes   |
>     |                                      | | help message.                   |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
> +   | neigh add ipv4 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
> +   |                                      | | information into                |         |          |
> +   |                                      | | ``ipv4_rewrite`` node.          |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | neigh add ipv6 <ip> <mac>            | | Command to add a neighbour      |   Yes   |    Yes   |
> +   |                                      | | information into                |         |          |
> +   |                                      | | ``ipv6_rewrite`` node.          |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
> +   |                                      | | message.                        |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
>
>  Runtime configuration
>  ---------------------
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 09/12] app/graph: support ethdev Rx command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 09/12] app/graph: support ethdev Rx " skori
@ 2023-10-23  7:05                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:05 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 1:27 AM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> Adds ethdev_rx module to create port-queue-core mapping.
>
> Mapping will be used to launch graph worker thread and dequeue
> packets on mentioned core from desired port/queue.
>
> Following commands are exposed:
>  - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
>  - help ethdev_rx
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   2 +
>  app/graph/ethdev_rx.c      | 165 +++++++++++++++++++++++++++++++++++++
>  app/graph/ethdev_rx.h      |  37 +++++++++
>  app/graph/ethdev_rx_priv.h |  39 +++++++++
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   1 +
>  doc/guides/tools/graph.rst |  10 +++
>  7 files changed, 255 insertions(+)
>  create mode 100644 app/graph/ethdev_rx.c
>  create mode 100644 app/graph/ethdev_rx.h
>  create mode 100644 app/graph/ethdev_rx_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index f564362da1..ad7d7deadf 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -30,6 +30,8 @@ cmdline_parse_ctx_t modules_ctx[] = {
>         (cmdline_parse_inst_t *)&ethdev_ip6_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_help_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_rx_cmd_ctx,
> +       (cmdline_parse_inst_t *)&ethdev_rx_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv4_lookup_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv4_lookup_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&ipv6_lookup_cmd_ctx,
> diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
> new file mode 100644
> index 0000000000..f2cb8cf9a5
> --- /dev/null
> +++ b/app/graph/ethdev_rx.c
> @@ -0,0 +1,165 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_ethdev.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>";
> +
> +static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +struct lcore_params *lcore_params = lcore_params_array;
> +struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +uint16_t nb_lcore_params;
> +
> +static void
> +rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
> +{
> +       uint8_t n_rx_queue;
> +
> +       n_rx_queue = lcore_conf[core].n_rx_queue;
> +       lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
> +       lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
> +       lcore_conf[core].n_rx_queue++;
> +}
> +
> +uint8_t
> +ethdev_rx_num_rx_queues_get(uint16_t port)
> +{
> +       int queue = -1;
> +       uint16_t i;
> +
> +       for (i = 0; i < nb_lcore_params; ++i) {
> +               if (lcore_params[i].port_id == port) {
> +                       if (lcore_params[i].queue_id == queue + 1)
> +                               queue = lcore_params[i].queue_id;
> +                       else
> +                               rte_exit(EXIT_FAILURE,
> +                                        "Queue ids of the port %d must be"
> +                                        " in sequence and must start with 0\n",
> +                                        lcore_params[i].port_id);
> +               }
> +       }
> +
> +       return (uint8_t)(++queue);
> +}
> +
> +static int
> +ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
> +{
> +       uint64_t coremask;
> +       uint16_t port_id;
> +       int rc;
> +
> +       if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
> +               return -EINVAL;
> +
> +       rc = rte_eth_dev_get_port_by_name(name, &port_id);
> +       if (rc)
> +               return -EINVAL;
> +
> +       coremask = 0xff; /* FIXME: Read from graph configuration */
> +
> +       if (!(coremask & (1 << core)))
> +               return -EINVAL;
> +
> +       rx_map_configure(port_id, queue, core);
> +
> +       lcore_params_array[nb_lcore_params].port_id = port_id;
> +       lcore_params_array[nb_lcore_params].queue_id = queue;
> +       lcore_params_array[nb_lcore_params].lcore_id = core;
> +       nb_lcore_params++;
> +       return 0;
> +}
> +
> +static void
> +cli_ethdev_rx_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +                  __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n",
> +                "----------------------------- ethdev_rx command help -----------------------------",
> +                cmd_ethdev_rx_help);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +static void
> +cli_ethdev_rx(void *parsed_result, __rte_unused struct cmdline *cl, void *data __rte_unused)
> +{
> +       struct ethdev_rx_cmd_tokens *res = parsed_result;
> +       int rc = -EINVAL;
> +
> +       rc = ethdev_rx_map_add(res->dev, res->qid, res->core_id);
> +       if (rc < 0) {
> +               cli_exit();
> +               printf(MSG_CMD_FAIL, res->cmd);
> +               rte_exit(EXIT_FAILURE, "input core is Invalid\n");
> +       }
> +
> +}
> +
> +cmdline_parse_token_string_t ethdev_rx_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, cmd, "ethdev_rx");
> +cmdline_parse_token_string_t ethdev_rx_map =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, map, "map");
> +cmdline_parse_token_string_t ethdev_rx_port =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, port, "port");
> +cmdline_parse_token_string_t ethdev_rx_dev =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, dev, NULL);
> +cmdline_parse_token_string_t ethdev_rx_queue =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, queue, "queue");
> +cmdline_parse_token_num_t ethdev_rx_qid =
> +       TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, qid, RTE_UINT32);
> +cmdline_parse_token_string_t ethdev_rx_core =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_cmd_tokens, core, "core");
> +cmdline_parse_token_num_t ethdev_rx_core_id =
> +       TOKEN_NUM_INITIALIZER(struct ethdev_rx_cmd_tokens, core_id, RTE_UINT32);
> +
> +cmdline_parse_inst_t ethdev_rx_cmd_ctx = {
> +       .f = cli_ethdev_rx,
> +       .data = NULL,
> +       .help_str = cmd_ethdev_rx_help,
> +       .tokens = {
> +               (void *)&ethdev_rx_cmd,
> +               (void *)&ethdev_rx_map,
> +               (void *)&ethdev_rx_port,
> +               (void *)&ethdev_rx_dev,
> +               (void *)&ethdev_rx_queue,
> +               (void *)&ethdev_rx_qid,
> +               (void *)&ethdev_rx_core,
> +               (void *)&ethdev_rx_core_id,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t ethdev_rx_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, cmd, "help");
> +cmdline_parse_token_string_t ethdev_rx_help_module =
> +       TOKEN_STRING_INITIALIZER(struct ethdev_rx_help_cmd_tokens, module, "ethdev_rx");
> +
> +cmdline_parse_inst_t ethdev_rx_help_cmd_ctx = {
> +       .f = cli_ethdev_rx_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&ethdev_rx_help_cmd,
> +               (void *)&ethdev_rx_help_module,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
> new file mode 100644
> index 0000000000..8e7b31448c
> --- /dev/null
> +++ b/app/graph/ethdev_rx.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_H
> +#define APP_GRAPH_ETHDEV_RX_H
> +
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
> +#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
> +
> +struct lcore_rx_queue {
> +       uint16_t port_id;
> +       uint8_t queue_id;
> +       char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +struct lcore_conf {
> +       uint16_t n_rx_queue;
> +       struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
> +       struct rte_graph *graph;
> +       char name[RTE_GRAPH_NAMESIZE];
> +       rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +extern cmdline_parse_inst_t ethdev_rx_help_cmd_ctx;
> +extern cmdline_parse_inst_t ethdev_rx_cmd_ctx;
> +extern struct lcore_params *lcore_params;
> +extern uint16_t nb_lcore_params;
> +
> +#endif
> diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
> new file mode 100644
> index 0000000000..5d155be043
> --- /dev/null
> +++ b/app/graph/ethdev_rx_priv.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
> +#define APP_GRAPH_ETHDEV_RX_PRIV_H
> +
> +#include <stdint.h>
> +
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#define MAX_RX_QUEUE_PER_PORT 128
> +#define MAX_JUMBO_PKT_LEN  9600
> +#define NB_SOCKETS 8
> +
> +struct ethdev_rx_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t map;
> +       cmdline_fixed_string_t port;
> +       cmdline_fixed_string_t dev;
> +       cmdline_fixed_string_t queue;
> +       cmdline_fixed_string_t core;
> +       uint32_t core_id;
> +       uint32_t qid;
> +};
> +
> +struct ethdev_rx_help_cmd_tokens {
> +       cmdline_fixed_string_t cmd;
> +       cmdline_fixed_string_t module;
> +};
> +
> +struct lcore_params {
> +       uint16_t port_id;
> +       uint8_t queue_id;
> +       uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +#endif
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index 8fa9d605b9..d8391d5cae 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -12,6 +12,7 @@ deps += ['graph', 'eal', 'lpm', 'ethdev', 'node', 'cmdline']
>  sources = files(
>          'cli.c',
>          'conn.c',
> +        'ethdev_rx.c',
>          'ethdev.c',
>          'ip4_route.c',
>          'ip6_route.c',
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index e9e42da7cc..56b7c94ecc 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -11,6 +11,7 @@
>  #include "cli.h"
>  #include "conn.h"
>  #include "ethdev.h"
> +#include "ethdev_rx.h"
>  #include "mempool.h"
>  #include "neigh.h"
>  #include "route.h"
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index b1bd2e6048..318d92a0fb 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -141,6 +141,16 @@ file to express the requested use case configuration.
>     | help neigh                           | | Command to dump neigh help      |   Yes   |    Yes   |
>     |                                      | | message.                        |         |          |
>     +--------------------------------------+-----------------------------------+---------+----------+
> +   | | ethdev_rx map port <ethdev_name>   | | Command to add port-queue-core  |   No    |    No    |
> +   | | queue <q_num> core <core_id>       | | mapping to ``ethdev_rx`` node.  |         |          |
> +   |                                      | | ``ethdev_rx`` node instance will|         |          |
> +   |                                      | | be pinned on given core and will|         |          |
> +   |                                      | | poll on requested port/queue    |         |          |
> +   |                                      | | pair.                           |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help ethdev_rx                       | | Command to dump ethdev_rx help  |   Yes   |    Yes   |
> +   |                                      | | message.                        |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
>
>  Runtime configuration
>  ---------------------
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 10/12] app/graph: support graph command line interfaces
  2023-10-19 17:30                                   ` [PATCH v11 10/12] app/graph: support graph " skori
@ 2023-10-23  7:06                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:06 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Thu, Oct 19, 2023 at 11:12 PM <skori@marvell.com> wrote:
>
> From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
>
> Adds graph module to create a graph for a given use case like
> l3fwd.
>
> Following commands are exposed:
>  - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] \
>         model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num> \
>         pcap_file <output_capture_file>
>  - graph start
>  - graph stats show
>  - help graph
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
>  app/graph/cli.c            |   4 +
>  app/graph/ethdev_rx.c      |   2 +-
>  app/graph/graph.c          | 550 +++++++++++++++++++++++++++++++++++++
>  app/graph/graph.h          |  21 ++
>  app/graph/graph_priv.h     |  70 +++++
>  app/graph/ip4_route.c      |   5 +-
>  app/graph/ip6_route.c      |   5 +-
>  app/graph/meson.build      |   1 +
>  app/graph/module_api.h     |   1 +
>  app/graph/neigh.c          |  10 +-
>  doc/guides/tools/graph.rst |  19 ++
>  11 files changed, 683 insertions(+), 5 deletions(-)
>  create mode 100644 app/graph/graph.c
>  create mode 100644 app/graph/graph.h
>  create mode 100644 app/graph/graph_priv.h
>
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> index ad7d7deadf..30b12312d6 100644
> --- a/app/graph/cli.c
> +++ b/app/graph/cli.c
> @@ -20,6 +20,10 @@
>  #define MAX_LINE_SIZE 2048
>
>  cmdline_parse_ctx_t modules_ctx[] = {
> +       (cmdline_parse_inst_t *)&graph_config_cmd_ctx,
> +       (cmdline_parse_inst_t *)&graph_start_cmd_ctx,
> +       (cmdline_parse_inst_t *)&graph_stats_cmd_ctx,
> +       (cmdline_parse_inst_t *)&graph_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&mempool_config_cmd_ctx,
>         (cmdline_parse_inst_t *)&mempool_help_cmd_ctx,
>         (cmdline_parse_inst_t *)&ethdev_show_cmd_ctx,
> diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
> index f2cb8cf9a5..03f8effcca 100644
> --- a/app/graph/ethdev_rx.c
> +++ b/app/graph/ethdev_rx.c
> @@ -69,7 +69,7 @@ ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
>         if (rc)
>                 return -EINVAL;
>
> -       coremask = 0xff; /* FIXME: Read from graph configuration */
> +       coremask = graph_coremask_get();
>
>         if (!(coremask & (1 << core)))
>                 return -EINVAL;
> diff --git a/app/graph/graph.c b/app/graph/graph.c
> new file mode 100644
> index 0000000000..74a99dd68e
> --- /dev/null
> +++ b/app/graph/graph.c
> @@ -0,0 +1,550 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cmdline_parse.h>
> +#include <cmdline_parse_num.h>
> +#include <cmdline_parse_string.h>
> +#include <cmdline_socket.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph_worker.h>
> +#include <rte_log.h>
> +
> +#include "graph_priv.h"
> +#include "module_api.h"
> +
> +#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
> +
> +static const char
> +cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
> +                  "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
> +                  "pcap_file <output_capture_file>";
> +
> +static const char * const supported_usecases[] = {"l3fwd"};
> +struct graph_config graph_config;
> +bool graph_started;
> +
> +/* Check the link rc of all ports in up to 9s, and print them finally */
> +static void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> +       char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
> +       uint8_t count, all_ports_up, print_flag = 0;
> +       struct rte_eth_link link;
> +       uint16_t portid;
> +       int rc;
> +
> +       printf("\nChecking link status...");
> +       fflush(stdout);
> +       for (count = 0; count <= MAX_CHECK_TIME; count++) {
> +               if (force_quit)
> +                       return;
> +
> +               all_ports_up = 1;
> +               RTE_ETH_FOREACH_DEV(portid)
> +               {
> +                       if (force_quit)
> +                               return;
> +
> +                       if ((port_mask & (1 << portid)) == 0)
> +                               continue;
> +
> +                       memset(&link, 0, sizeof(link));
> +                       rc = rte_eth_link_get_nowait(portid, &link);
> +                       if (rc < 0) {
> +                               all_ports_up = 0;
> +                               if (print_flag == 1)
> +                                       printf("Port %u link get failed: %s\n",
> +                                              portid, rte_strerror(-rc));
> +                               continue;
> +                       }
> +
> +                       /* Print link rc if flag set */
> +                       if (print_flag == 1) {
> +                               rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
> +                                       &link);
> +                               printf("Port %d %s\n", portid, link_rc_text);
> +                               continue;
> +                       }
> +
> +                       /* Clear all_ports_up flag if any link down */
> +                       if (link.link_status == RTE_ETH_LINK_DOWN) {
> +                               all_ports_up = 0;
> +                               break;
> +                       }
> +               }
> +
> +               /* After finally printing all link rc, get out */
> +               if (print_flag == 1)
> +                       break;
> +
> +               if (all_ports_up == 0) {
> +                       printf(".");
> +                       fflush(stdout);
> +                       rte_delay_ms(CHECK_INTERVAL);
> +               }
> +
> +               /* Set the print_flag if all ports up or timeout */
> +               if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> +                       print_flag = 1;
> +                       printf("Done\n");
> +               }
> +       }
> +}
> +
> +static bool
> +parser_usecases_read(char *usecases)
> +{
> +       bool valid = false;
> +       uint32_t i, j = 0;
> +       char *token;
> +
> +       token = strtok(usecases, ",");
> +       while (token != NULL) {
> +               for (i = 0; i < RTE_DIM(supported_usecases); i++) {
> +                       if (strcmp(supported_usecases[i], token) == 0) {
> +                               graph_config.usecases[j].enabled = true;
> +                               rte_strscpy(graph_config.usecases[j].name, token, 31);
> +                               valid = true;
> +                               j++;
> +                               break;
> +                       }
> +               }
> +               token = strtok(NULL, ",");
> +       }
> +
> +       return valid;
> +}
> +
> +static uint64_t
> +graph_worker_count_get(void)
> +{
> +       uint64_t nb_worker = 0;
> +       uint64_t coremask;
> +
> +       coremask = graph_config.params.coremask;
> +       while (coremask) {
> +               if (coremask & 0x1)
> +                       nb_worker++;
> +
> +               coremask = (coremask >> 1);
> +       }
> +
> +       return nb_worker;
> +}
> +
> +static struct rte_node_ethdev_config *
> +graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
> +{
> +       uint32_t n_tx_queue, nb_conf = 0, lcore_id;
> +       uint16_t queueid, portid, nb_graphs = 0;
> +       uint8_t nb_rx_queue, queue;
> +       struct lcore_conf *qconf;
> +
> +       n_tx_queue = graph_worker_count_get();
> +       if (n_tx_queue > RTE_MAX_ETHPORTS)
> +               n_tx_queue = RTE_MAX_ETHPORTS;
> +
> +       RTE_ETH_FOREACH_DEV(portid) {
> +               /* Skip ports that are not enabled */
> +               if ((enabled_port_mask & (1 << portid)) == 0) {
> +                       printf("\nSkipping disabled port %d\n", portid);
> +                       continue;
> +               }
> +
> +               nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
> +
> +               /* Setup ethdev node config */
> +               ethdev_conf[nb_conf].port_id = portid;
> +               ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> +               ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
> +               ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
> +               ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
> +
> +               nb_conf++;
> +       }
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               if (rte_lcore_is_enabled(lcore_id) == 0)
> +                       continue;
> +
> +               qconf = &lcore_conf[lcore_id];
> +               printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> +               fflush(stdout);
> +
> +               /* Init RX queues */
> +               for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> +                       portid = qconf->rx_queue_list[queue].port_id;
> +                       queueid = qconf->rx_queue_list[queue].queue_id;
> +
> +                       /* Add this queue node to its graph */
> +                       snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
> +                                "ethdev_rx-%u-%u", portid, queueid);
> +               }
> +               if (qconf->n_rx_queue)
> +                       nb_graphs++;
> +       }
> +
> +       printf("\n");
> +
> +       ethdev_start();
> +       check_all_ports_link_status(enabled_port_mask);
> +
> +       *num_conf = nb_conf;
> +       *num_graphs = nb_graphs;
> +       return ethdev_conf;
> +}
> +
> +static void
> +graph_stats_print_to_file(void)
> +{
> +       struct rte_graph_cluster_stats_param s_param;
> +       struct rte_graph_cluster_stats *stats;
> +       const char *pattern = "worker_*";
> +       FILE *fp = NULL;
> +       size_t sz, len;
> +
> +       /* Prepare stats object */
> +       fp = fopen("/tmp/graph_stats.txt", "w+");
> +       if (fp == NULL)
> +               rte_exit(EXIT_FAILURE, "Error in opening stats file\n");
> +
> +       memset(&s_param, 0, sizeof(s_param));
> +       s_param.f = fp;
> +       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");
> +
> +       /* Clear screen and move to top left */
> +       rte_graph_cluster_stats_get(stats, 0);
> +       rte_delay_ms(1E3);
> +
> +       fseek(fp, 0L, SEEK_END);
> +       sz = ftell(fp);
> +       fseek(fp, 0L, SEEK_SET);
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +
> +       sz = fread(conn->msg_out, sizeof(char), sz, fp);
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +       rte_graph_cluster_stats_destroy(stats);
> +
> +       fclose(fp);
> +}
> +
> +static void
> +cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +               __rte_unused void *data)
> +{
> +       graph_stats_print_to_file();
> +}
> +
> +bool
> +graph_status_get(void)
> +{
> +       return graph_started;
> +}
> +
> +static void
> +cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +               __rte_unused void *data)
> +{
> +       struct rte_node_ethdev_config *conf;
> +       uint32_t nb_graphs = 0, nb_conf, i;
> +       int rc = -EINVAL;
> +
> +       conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
> +       for (i = 0; i < MAX_GRAPH_USECASES; i++) {
> +               if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
> +                       if (graph_config.usecases[i].enabled) {
> +                               RTE_SET_USED(conf);
> +                               break;
> +                       }
> +               }
> +       }
> +
> +       if (!rc)
> +               graph_started = true;
> +}
> +
> +static int
> +graph_config_add(char *usecases, struct graph_config *config)
> +{
> +       uint64_t lcore_id, core_num;
> +       uint64_t eal_coremask = 0;
> +
> +       if (!parser_usecases_read(usecases))
> +               return -EINVAL;
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               if (rte_lcore_is_enabled(lcore_id))
> +                       eal_coremask |= RTE_BIT64(lcore_id);
> +       }
> +
> +       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> +               core_num = 1 << lcore_id;
> +               if (config->params.coremask & core_num) {
> +                       if (eal_coremask & core_num)
> +                               continue;
> +                       else
> +                               return -EINVAL;
> +               }
> +       }
> +
> +       graph_config.params.bsz = config->params.bsz;
> +       graph_config.params.tmo = config->params.tmo;
> +       graph_config.params.coremask = config->params.coremask;
> +       graph_config.model = config->model;
> +       graph_config.pcap_ena = config->pcap_ena;
> +       graph_config.num_pcap_pkts = config->num_pcap_pkts;
> +       graph_config.pcap_file = strdup(config->pcap_file);
> +
> +       return 0;
> +}
> +
> +void
> +graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
> +{
> +
> +       *pcap_ena = graph_config.pcap_ena;
> +       *num_pkts = graph_config.num_pcap_pkts;
> +       *file = graph_config.pcap_file;
> +}
> +
> +int
> +graph_walk_start(void *conf)
> +{
> +       struct lcore_conf *qconf;
> +       struct rte_graph *graph;
> +       uint32_t lcore_id;
> +
> +       RTE_SET_USED(conf);
> +
> +       lcore_id = rte_lcore_id();
> +       qconf = &lcore_conf[lcore_id];
> +       graph = qconf->graph;
> +
> +       if (!graph) {
> +               RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
> +               return 0;
> +       }
> +
> +       RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
> +               qconf->name, graph);
> +
> +       while (likely(!force_quit))
> +               rte_graph_walk(graph);
> +
> +       return 0;
> +}
> +
> +void
> +graph_stats_print(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 = "worker_*";
> +
> +       /* 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);
> +               if (app_graph_exit())
> +                       force_quit = true;
> +       }
> +
> +       rte_graph_cluster_stats_destroy(stats);
> +}
> +
> +uint64_t
> +graph_coremask_get(void)
> +{
> +       return graph_config.params.coremask;
> +}
> +
> +static void
> +cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
> +{
> +       struct graph_config_cmd_tokens *res = parsed_result;
> +       struct graph_config config;
> +       char *model_name;
> +       uint8_t model;
> +       int rc;
> +
> +       model_name = res->model_name;
> +       if (strcmp(model_name, "default") == 0) {
> +               model = GRAPH_MODEL_RTC;
> +       } else if (strcmp(model_name, "rtc") == 0) {
> +               model = GRAPH_MODEL_RTC;
> +       } else if (strcmp(model_name, "mcd") == 0) {
> +               model = GRAPH_MODEL_MCD;
> +       } else {
> +               printf(MSG_ARG_NOT_FOUND, "model arguments");
> +               return;
> +       }
> +
> +       config.params.bsz = res->size;
> +       config.params.tmo = res->ns;
> +       config.params.coremask = res->mask;
> +       config.model = model;
> +       config.pcap_ena = res->pcap_ena;
> +       config.num_pcap_pkts = res->num_pcap_pkts;
> +       config.pcap_file = res->pcap_file;
> +       rc = graph_config_add(res->usecase, &config);
> +       if (rc < 0) {
> +               cli_exit();
> +               printf(MSG_CMD_FAIL, res->graph);
> +               rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
> +       }
> +}
> +
> +static void
> +cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
> +              __rte_unused void *data)
> +{
> +       size_t len;
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out += len;
> +       snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n",
> +                "----------------------------- graph command help -----------------------------",
> +                cmd_graph_help, "graph start", "graph stats show");
> +
> +       len = strlen(conn->msg_out);
> +       conn->msg_out_len_max -= len;
> +}
> +
> +cmdline_parse_token_string_t graph_display_graph =
> +       TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
> +cmdline_parse_token_string_t graph_display_stats =
> +       TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
> +cmdline_parse_token_string_t graph_display_show =
> +       TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
> +
> +cmdline_parse_inst_t graph_stats_cmd_ctx = {
> +       .f = cli_graph_stats,
> +       .data = NULL,
> +       .help_str = "graph stats show",
> +       .tokens = {
> +               (void *)&graph_display_graph,
> +               (void *)&graph_display_stats,
> +               (void *)&graph_display_show,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t graph_config_start_graph =
> +       TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
> +cmdline_parse_token_string_t graph_config_start =
> +       TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
> +
> +cmdline_parse_inst_t graph_start_cmd_ctx = {
> +       .f = cli_graph_start,
> +       .data = NULL,
> +       .help_str = "graph start",
> +       .tokens = {
> +               (void *)&graph_config_start_graph,
> +               (void *)&graph_config_start,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t graph_config_add_graph =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
> +cmdline_parse_token_string_t graph_config_add_usecase =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
> +cmdline_parse_token_string_t graph_config_add_coremask =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
> +cmdline_parse_token_num_t graph_config_add_mask =
> +       TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
> +cmdline_parse_token_string_t graph_config_add_bsz =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
> +cmdline_parse_token_num_t graph_config_add_size =
> +       TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
> +cmdline_parse_token_string_t graph_config_add_tmo =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
> +cmdline_parse_token_num_t graph_config_add_ns =
> +       TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
> +cmdline_parse_token_string_t graph_config_add_model =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
> +cmdline_parse_token_string_t graph_config_add_model_name =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
> +cmdline_parse_token_string_t graph_config_add_capt_ena =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
> +cmdline_parse_token_num_t graph_config_add_pcap_ena =
> +       TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
> +cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
> +cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
> +       TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
> +cmdline_parse_token_string_t graph_config_add_capt_file =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
> +cmdline_parse_token_string_t graph_config_add_pcap_file =
> +       TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
> +
> +cmdline_parse_inst_t graph_config_cmd_ctx = {
> +       .f = cli_graph,
> +       .data = NULL,
> +       .help_str = cmd_graph_help,
> +       .tokens = {
> +               (void *)&graph_config_add_graph,
> +               (void *)&graph_config_add_usecase,
> +               (void *)&graph_config_add_coremask,
> +               (void *)&graph_config_add_mask,
> +               (void *)&graph_config_add_bsz,
> +               (void *)&graph_config_add_size,
> +               (void *)&graph_config_add_tmo,
> +               (void *)&graph_config_add_ns,
> +               (void *)&graph_config_add_model,
> +               (void *)&graph_config_add_model_name,
> +               (void *)&graph_config_add_capt_ena,
> +               (void *)&graph_config_add_pcap_ena,
> +               (void *)&graph_config_add_capt_pkts_count,
> +               (void *)&graph_config_add_num_pcap_pkts,
> +               (void *)&graph_config_add_capt_file,
> +               (void *)&graph_config_add_pcap_file,
> +               NULL,
> +       },
> +};
> +
> +cmdline_parse_token_string_t graph_help_cmd =
> +       TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
> +cmdline_parse_token_string_t graph_help_graph =
> +       TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
> +
> +cmdline_parse_inst_t graph_help_cmd_ctx = {
> +       .f = cli_graph_help,
> +       .data = NULL,
> +       .help_str = "",
> +       .tokens = {
> +               (void *)&graph_help_cmd,
> +               (void *)&graph_help_graph,
> +               NULL,
> +       },
> +};
> diff --git a/app/graph/graph.h b/app/graph/graph.h
> new file mode 100644
> index 0000000000..a14fa37ccd
> --- /dev/null
> +++ b/app/graph/graph.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_H
> +#define APP_GRAPH_H
> +
> +#include <cmdline_parse.h>
> +
> +extern cmdline_parse_inst_t graph_config_cmd_ctx;
> +extern cmdline_parse_inst_t graph_start_cmd_ctx;
> +extern cmdline_parse_inst_t graph_stats_cmd_ctx;
> +extern cmdline_parse_inst_t graph_help_cmd_ctx;
> +
> +int graph_walk_start(void *conf);
> +void graph_stats_print(void);
> +void graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file);
> +uint64_t graph_coremask_get(void);
> +bool graph_status_get(void);
> +
> +#endif
> diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
> new file mode 100644
> index 0000000000..a48a35daa3
> --- /dev/null
> +++ b/app/graph/graph_priv.h
> @@ -0,0 +1,70 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_PRIV_H
> +#define APP_GRAPH_PRIV_H
> +
> +#define MAX_GRAPH_USECASES 32
> +
> +struct graph_help_cmd_tokens {
> +       cmdline_fixed_string_t help;
> +       cmdline_fixed_string_t graph;
> +};
> +
> +struct graph_start_cmd_tokens {
> +       cmdline_fixed_string_t graph;
> +       cmdline_fixed_string_t start;
> +};
> +
> +struct graph_stats_cmd_tokens {
> +       cmdline_fixed_string_t show;
> +       cmdline_fixed_string_t graph;
> +       cmdline_fixed_string_t stats;
> +};
> +
> +struct graph_config_cmd_tokens {
> +       cmdline_fixed_string_t graph;
> +       cmdline_fixed_string_t usecase;
> +       cmdline_fixed_string_t bsz;
> +       cmdline_fixed_string_t tmo;
> +       cmdline_fixed_string_t coremask;
> +       cmdline_fixed_string_t model;
> +       cmdline_fixed_string_t capt_ena;
> +       cmdline_fixed_string_t capt_pkts_count;
> +       cmdline_fixed_string_t capt_file;
> +       cmdline_fixed_string_t model_name;
> +       cmdline_fixed_string_t pcap_file;
> +       uint16_t size;
> +       uint64_t ns;
> +       uint64_t mask;
> +       uint64_t num_pcap_pkts;
> +       uint8_t pcap_ena;
> +};
> +
> +enum graph_model {
> +       GRAPH_MODEL_RTC = 0x01,
> +       GRAPH_MODEL_MCD = 0x02,
> +};
> +
> +struct usecases {
> +       char name[32];
> +       bool enabled;
> +};
> +
> +struct usecase_params {
> +       uint64_t coremask;
> +       uint32_t bsz;
> +       uint32_t tmo;
> +};
> +
> +struct graph_config {
> +       struct usecases usecases[MAX_GRAPH_USECASES];
> +       struct usecase_params params;
> +       enum graph_model model;
> +       uint64_t num_pcap_pkts;
> +       char *pcap_file;
> +       uint8_t pcap_ena;
> +};
> +
> +#endif
> diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
> index db3354c270..fc83586427 100644
> --- a/app/graph/ip4_route.c
> +++ b/app/graph/ip4_route.c
> @@ -97,11 +97,14 @@ route_ip4_add(struct route_ipv4_config *route)
>         ipv4route->via = route->via;
>         ipv4route->is_used = true;
>
> -       /* FIXME: Get graph status here and then update table */
> +       if (!graph_status_get())
> +               goto exit;
> +
>         rc = route4_rewirte_table_update(ipv4route);
>         if (rc)
>                 goto free;
>
> +exit:
>         TAILQ_INSERT_TAIL(&route4, ipv4route, next);
>         return 0;
>  free:
> diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
> index e793cde830..1fa4865220 100644
> --- a/app/graph/ip6_route.c
> +++ b/app/graph/ip6_route.c
> @@ -102,11 +102,14 @@ route_ip6_add(struct route_ipv6_config *route)
>         }
>         ipv6route->is_used = true;
>
> -       /* FIXME: Get graph status here and then update table */
> +       if (!graph_status_get())
> +               goto exit;
> +
>         rc = route6_rewirte_table_update(ipv6route);
>         if (rc)
>                 goto free;
>
> +exit:
>         TAILQ_INSERT_TAIL(&route6, ipv6route, next);
>         return 0;
>  free:
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> index d8391d5cae..15d16a302e 100644
> --- a/app/graph/meson.build
> +++ b/app/graph/meson.build
> @@ -14,6 +14,7 @@ sources = files(
>          'conn.c',
>          'ethdev_rx.c',
>          'ethdev.c',
> +        'graph.c',
>          'ip4_route.c',
>          'ip6_route.c',
>          'main.c',
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index 56b7c94ecc..392dcfb222 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -12,6 +12,7 @@
>  #include "conn.h"
>  #include "ethdev.h"
>  #include "ethdev_rx.h"
> +#include "graph.h"
>  #include "mempool.h"
>  #include "neigh.h"
>  #include "route.h"
> diff --git a/app/graph/neigh.c b/app/graph/neigh.c
> index 0cee502719..22be7361e3 100644
> --- a/app/graph/neigh.c
> +++ b/app/graph/neigh.c
> @@ -154,11 +154,14 @@ neigh_ip4_add(uint32_t ip, uint64_t mac)
>         v4_config->mac = mac;
>         v4_config->is_used = true;
>
> -       /* FIXME: Get graph status here and then update table */
> +       if (!graph_status_get())
> +               goto exit;
> +
>         rc = ip4_rewrite_node_add(v4_config);
>         if (rc)
>                 goto free;
>
> +exit:
>         TAILQ_INSERT_TAIL(&neigh4, v4_config, next);
>         return 0;
>  free:
> @@ -187,11 +190,14 @@ neigh_ip6_add(uint8_t *ip, uint64_t mac)
>         v6_config->mac = mac;
>         v6_config->is_used = true;
>
> -       /* FIXME: Get graph status here and then update table */
> +       if (!graph_status_get())
> +               goto exit;
> +
>         rc =  ip6_rewrite_node_add(v6_config);
>         if (rc)
>                 goto free;
>
> +exit:
>         TAILQ_INSERT_TAIL(&neigh6, v6_config, next);
>         return 0;
>  free:
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 318d92a0fb..08ec57b7f8 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -71,6 +71,25 @@ file to express the requested use case configuration.
>     +--------------------------------------+-----------------------------------+---------+----------+
>     |               Command                |             Description           | Dynamic | Optional |
>     +======================================+===================================+=========+==========+
> +   | | graph <usecases> [bsz <size>]      | | Command to express the desired  |   No    |    No    |
> +   | | [tmo <ns>] [coremask <bitmask>]    | | use case. Also enables/disable  |         |          |
> +   | | model <rtc/mcd/default> pcap_enable| | pcap capturing.                 |         |          |
> +   | | <0/1> num_pcap_pkts <num> pcap_file|                                   |         |          |
> +   | | <output_capture_file>              |                                   |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | graph start                          | | Command to start the graph.     |   No    |    No    |
> +   |                                      | | This command triggers that no   |         |          |
> +   |                                      | | more commands are left to be    |         |          |
> +   |                                      | | parsed and graph initialization |         |          |
> +   |                                      | | can be started now. It must be  |         |          |
> +   |                                      | | the last command in usecase.cli |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | graph stats show                     | | Command to dump current graph   |   Yes   |    Yes   |
> +   |                                      | | statistics.                     |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
> +   | help graph                           | | Command to dump graph help      |   Yes   |    Yes   |
> +   |                                      | | message.                        |         |          |
> +   +--------------------------------------+-----------------------------------+---------+----------+
>     | | mempool <mempool_name> size        | | Command to create mempool which |   No    |    No    |
>     | | <mbuf_size> buffers                | | will be further associated to   |         |          |
>     | | <number_of_buffers>                | | RxQ to dequeue the packets.     |         |          |
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 11/12] app/graph: support CLI option to enable graph stats
  2023-10-19 17:30                                   ` [PATCH v11 11/12] app/graph: support CLI option to enable graph stats skori
@ 2023-10-23  7:06                                     ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:06 UTC (permalink / raw)
  To: skori; +Cc: Rakesh Kudurumalla, dev, Jerin Jacob

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 2:07 AM <skori@marvell.com> wrote:
>
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Adds application's command line parameter "--enable-graph-stats"
> to enable dumping graph stats on console.
>
> By default, no graph stats will be printed on console but same can
> be dumped via telnet session using "graph stats show" command.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Acked-by: Jerin Jacob <jerinj@marvell.com>
> ---
>  app/graph/main.c           | 17 ++++++++++++++++-
>  app/graph/module_api.h     |  2 ++
>  doc/guides/tools/graph.rst |  4 ++++
>  3 files changed, 22 insertions(+), 1 deletion(-)
>
> diff --git a/app/graph/main.c b/app/graph/main.c
> index c1cb435588..465376425c 100644
> --- a/app/graph/main.c
> +++ b/app/graph/main.c
> @@ -21,12 +21,13 @@
>  volatile bool force_quit;
>  struct conn *conn;
>
> -static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] "
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-stats] "
>                             "[--help]\n";
>
>  static struct app_params {
>         struct conn_params conn;
>         char *script_name;
> +       bool enable_graph_stats;
>  } app = {
>         .conn = {
>                 .welcome = "\nWelcome!\n\n",
> @@ -40,6 +41,7 @@ static struct app_params {
>                 .msg_handle_arg = NULL, /* set later. */
>         },
>         .script_name = NULL,
> +       .enable_graph_stats = false,
>  };
>
>  static void
> @@ -56,6 +58,7 @@ app_args_parse(int argc, char **argv)
>  {
>         struct option lgopts[] = {
>                 {"help", 0, 0, 'H'},
> +               {"enable-graph-stats", 0, 0, 'g'},
>         };
>         int h_present, p_present, s_present, n_args, i;
>         char *app_name = argv[0];
> @@ -133,6 +136,12 @@ app_args_parse(int argc, char **argv)
>                         }
>                         break;
>
> +               case 'g':
> +                       app.enable_graph_stats = true;
> +                       printf("WARNING! Telnet session can not be accessed with"
> +                              "--enable-graph-stats");
> +                       break;
> +
>                 case 'H':
>                 default:
>                         printf(usage, app_name);
> @@ -144,6 +153,12 @@ app_args_parse(int argc, char **argv)
>         return 0;
>  }
>
> +bool
> +app_graph_stats_enabled(void)
> +{
> +       return app.enable_graph_stats;
> +}
> +
>  bool
>  app_graph_exit(void)
>  {
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> index 392dcfb222..a7d287f5c8 100644
> --- a/app/graph/module_api.h
> +++ b/app/graph/module_api.h
> @@ -24,5 +24,7 @@
>  extern volatile bool force_quit;
>  extern struct conn *conn;
>
> +bool app_graph_stats_enabled(void);
>  bool app_graph_exit(void);
> +
>  #endif
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> index 08ec57b7f8..bd8611a3d0 100644
> --- a/doc/guides/tools/graph.rst
> +++ b/doc/guides/tools/graph.rst
> @@ -55,6 +55,10 @@ Following are the application command-line options:
>          a mandatory parameter which will be used to create desired graph
>          for a given use case.
>
> +* ``--enable-graph-stats``
> +
> +       Enable graph statistics printing on console. By default graph statistics are disabled.
> +
>  * ``--help``
>
>         Dumps application usage
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [EXT] [PATCH v10 12/12] app/graph: support l3fwd use case
  2023-10-19 12:28                                 ` [EXT] " Jerin Jacob Kollanukkaran
@ 2023-10-23  7:06                                   ` Nithin Dabilpuram
  0 siblings, 0 replies; 182+ messages in thread
From: Nithin Dabilpuram @ 2023-10-23  7:06 UTC (permalink / raw)
  To: Jerin Jacob Kollanukkaran; +Cc: Sunil Kumar Kori, Rakesh Kudurumalla, dev

Acked-By: Nithin Dabilpuram <ndabilpuram@marvell.com>

On Fri, Oct 20, 2023 at 5:47 AM Jerin Jacob Kollanukkaran
<jerinj@marvell.com> wrote:
>
> > -----Original Message-----
> > From: skori@marvell.com <skori@marvell.com>
> > Sent: Thursday, October 19, 2023 4:20 PM
> > To: Sunil Kumar Kori <skori@marvell.com>; Rakesh Kudurumalla
> > <rkudurumalla@marvell.com>
> > Cc: dev@dpdk.org
> > Subject: [EXT] [PATCH v10 12/12] app/graph: support l3fwd use case
> >
> > External Email
> >
> > ----------------------------------------------------------------------
> > From: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> >
> > Adds an use case l3fwd. It contains a dedicated l3fwd.cli file mentioning
> > commands to configure the required resources.
> >
> > Once application successfully parses the l3fwd.cli then a graph is created having
> > below nodes:
> >  - ethdev_rx -> pkt_cls
> >
> >  - pkt_cls -> ip4_lookup
> >  - pkt_cls -> ip6_lookup
> >  - pkt_cls -> pkt_drop
> >
> >  - ip4_lookup -> ip4_rewrite
> >  - ip4_lookup -> pkt_drop
> >
> >  - ip6_lookup -> ip6_rewrite
> >  - ip6_lookup -> pkt_drop
> >
> >  - ip4_rewrite -> ethdev_tx
> >  - ip4_rewrite -> pkt_drop
> >
> >  - ip6_rewrite -> ethdev_tx
> >  - ip6_rewrite -> pkt_drop
> >
> >  - ethdev_tx -> pkt_drop
> >
> > Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> > Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> > ---
> > +Supported Use cases
> > +-------------------
> > + * l3fwd
> > +
> > +This use case is supported for both PF and PCAP network devices. To
>
> Both HW and PCAP vdev network device.
> Remove PF instance form doc.
>
> > +demonstrate, corresponding .cli files are available at
> > +``<dpdk_root_dir/app/graph/examples/>``
> > +named as ``l3fwd.cli`` and  ``l3fwd_pcap.cli`` respectively.
> > +
> >  Running the Application
> >  -----------------------
> >
> > @@ -63,6 +71,26 @@ Following are the application command-line options:
> >
> >         Dumps application usage
> >
> > +Examples
> > +~~~~~~~~
>
> In order to have continuity,  Please move "16.1. Supported Use cases" here and remove "Examples"
>
> 16.x Supported Use case
> 16.x.1 L3fwd
> 16.x.1.1 Example commands
> 16.x.1.2 Verifying traffic
>
>
> > +
> > +For PF devices
> > +
> > +.. code-block:: console
> > +
> > +   ./dpdk-graph -c 0xff -a 0002:02:00.0 -a 0002:03:00.0 --
> > +             -s <dpdk_root_dir>/app/graph/examples/l3fwd.cli
> > +
> > +For net_pcapX devices
> > +
> > +.. code-block:: console
> > +
> > +   ./dpdk-graph -c 0xff --
> > vdev=net_pcap0,rx_pcap=in_net_pcap0.pcap,tx_pcap=out_net_pcap1.pcap
> > +                        --
> > vdev=net_pcap1,rx_pcap=in_net_pcap1.pcap,tx_pcap=out_net_pcap0.pcap
> > +                        -- -s
> > + <dpdk_root_dir>/app/graph/examples/l3fwd_pcap.cli
> > +
> > +Refer section :ref:`verifying_traffic` to create .pcap file used here.
> > +
> >  Supported CLI commands
> >  ----------------------
> >
> > @@ -223,3 +251,84 @@ Created graph for use case
> >
> >  On the successful execution of ``<usecase>.cli`` file, corresponding graph will
> > be created.
> >  This section mentions the created graph for each use case.
> > +
> > +l3fwd
> > +~~~~~
> > +
> > +.. _figure_l3fwd_graph:
> > +
> > +.. figure:: img/graph-usecase-l3fwd.*
> > +
> > +.. _verifying_traffic:
> > +
> > +Verifying traffic
> > +~~~~~~~~~~~~~~~~~
> > +
> > +``l3fwd.cli`` and ``l3fwd_pcap.cli`` creates setup with two network
> > +ports. Routing between these ports are done by lookup node routing
> > +information. For current use case, following routing table is used:
> > +
> > +.. code-block:: console
> > +
> > +   DIP        port
> > +   10.0.2.2    1
> > +   20.0.2.2    0
> > +
> > +On the successful execution of ``l3fwd.cli`` or ``l3fwd_pcap.cli``,
> > +user needs to send traffic with mentioned DIP.
> > +
> > +For net_pcapX devices, required pcap file should be created and passed
> > +to application. These pcap files can be created in several ways. Scapy is one of
> > the method to get the same:
> > +
> > +.. code-block:: console
> > +
> > +   # scapy
> > +
> > +                     aSPY//YASa
> > +             apyyyyCY//////////YCa       |
> > +            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
> > +    ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.3
> > +    AYAsAYYYYYYYY///Ps              cY//S   |
> > +         pCCCCY//p          cSSps y//Y   |
> > https://urldefense.proofpoint.com/v2/url?u=https-
> > 3A__github.com_secdev_scapy&d=DwIDAg&c=nKjWec2b6R0mOyPaz7xtfQ&r=
> > 1DGob4H4rxz6H8uITozGOCa0s5f4wCNtTa4UUKvcsvI&m=V_QcQyFL-
> > NxiuHEAdNEZlQ379HvK37suZH_8Yfuuz-HwEKmVw5Iy-SbtS95-
> > brBb&s=XR2R_CEDkRJPhayLXSY1ZRnzrZsuR-UDaSSDHELwAnQ&e=
> > +         SPPPP///a          pP///AC//Y   |
> > +              A//A            cyP////C   | Have fun!
> > +              p///Ac            sC///a   |
> > +              P////YCpc           A//A   | We are in France, we say Skappee.
> > +       scccccp///pSP///p          p//Y   | OK? Merci.
> > +      sY/////////y  caa           S//P   |             -- Sebastien Chabal
> > +       cayCyayP//Ya              pY/Ya   |
> > +        sY/PsY////YCc          aC//Yp
> > +         sc  sccaCY//PCypaapyCP//YSs
> > +                  spCPY//////YPSps
> > +                       ccaacs
> > +                                       using IPython 7.13.0
>
> This graphics can be removed in the doc
>
> > +   >>>
> > +   >>>
>
> Remove this
>
> > +   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="10.0.2.2")]
> > +   >>>
> > +   >>>wrpcap("in_net_pcap1.pcap",pkts)
> > +   >>>
> > +   >>>
>
> Remove above two lines
>
> > +   >>> pkts=[Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="29.0.0.1", dst="20.0.2.2"),
> > +             Ether(dst="FA:09:F9:D7:E0:9D",
> > src="10:70:1d:2f:42:2d")/IP(src="28.0.0.1", dst="20.0.2.2")]
> > +   >>>
> > +   >>>wrpcap("in_net_pcap0.pcap",pkts)
> > +   >>>
>
> Remove above line
>
> > +   >>> quit
>
> Which above changes:
> Acked-by: Jerin Jacob <jerinj@marvell.com>

^ permalink raw reply	[flat|nested] 182+ messages in thread

* RE: [PATCH v11 00/12] add CLI based graph application
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (11 preceding siblings ...)
  2023-10-19 17:30                                   ` [PATCH v11 12/12] app/graph: support l3fwd use case skori
@ 2023-10-23 13:03                                   ` Sunil Kumar Kori
  2023-11-03 16:44                                   ` Thomas Monjalon
  13 siblings, 0 replies; 182+ messages in thread
From: Sunil Kumar Kori @ 2023-10-23 13:03 UTC (permalink / raw)
  To: Thomas Monjalon; +Cc: dev, Jerin Jacob Kollanukkaran

Hi Thomas,

Following series is acked by @Nithin Kumar Dabilpuram & @Jerin Jacob Kollanukkaran and there are no review comments left. 
https://patches.dpdk.org/project/dpdk/cover/20231019173011.1186656-1-skori@marvell.com/

I would request you to pull this series for current release. 

Regards
Sunil Kumar Kori

> -----Original Message-----
> From: skori@marvell.com <skori@marvell.com>
> Sent: Thursday, October 19, 2023 11:00 PM
> Cc: dev@dpdk.org; Sunil Kumar Kori <skori@marvell.com>
> Subject: [PATCH v11 00/12] add CLI based graph application
> 
> From: Sunil Kumar Kori <skori@marvell.com>
> 
> In the continuation of following feedback
> https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-
> vattunuru@marvell.com/
> this patch series adds dpdk-graph application to exercise various usecases
> using graph.
> 
> 1. Each use case is defined in terms of .cli file which will contain set of
> commands to configure the system and to create a graph for that use case.
> 
> 2. Each module like ethdev, mempool, route etc exposes its set of commands
> to do global and node specific configuration.
> 
> 3. Command parsing is backed by command line library.
> 
> Rakesh Kudurumalla (5):
>   app/graph: support mempool command line interfaces
>   app/graph: support IPv6 lookup command line interfaces
>   app/graph: support ethdev Rx command line interfaces
>   app/graph: support graph command line interfaces
>   app/graph: support l3fwd use case
> 
> Sunil Kumar Kori (7):
>   app/graph: support application CLI framework
>   app/graph: support telnet connectivity framework
>   app/graph: support parser utility APIs
>   app/graph: support ethdev command line interfaces
>   app/graph: support IPv4 lookup command line interfaces
>   app/graph: support neigh command line interfaces
>   app/graph: support CLI option to enable graph stats
> 
>  MAINTAINERS                                  |   7 +
>  app/graph/cli.c                              | 138 +++
>  app/graph/cli.h                              |  32 +
>  app/graph/conn.c                             | 284 ++++++
>  app/graph/conn.h                             |  46 +
>  app/graph/ethdev.c                           | 890 +++++++++++++++++++
>  app/graph/ethdev.h                           |  40 +
>  app/graph/ethdev_priv.h                      | 112 +++
>  app/graph/ethdev_rx.c                        | 165 ++++
>  app/graph/ethdev_rx.h                        |  37 +
>  app/graph/ethdev_rx_priv.h                   |  39 +
>  app/graph/examples/l3fwd.cli                 |  73 ++
>  app/graph/examples/l3fwd_pcap.cli            |  71 ++
>  app/graph/graph.c                            | 550 ++++++++++++
>  app/graph/graph.h                            |  21 +
>  app/graph/graph_priv.h                       |  70 ++
>  app/graph/ip4_route.c                        | 224 +++++
>  app/graph/ip6_route.c                        | 229 +++++
>  app/graph/l3fwd.c                            | 136 +++
>  app/graph/l3fwd.h                            |  11 +
>  app/graph/main.c                             | 237 +++++
>  app/graph/mempool.c                          | 140 +++
>  app/graph/mempool.h                          |  24 +
>  app/graph/mempool_priv.h                     |  34 +
>  app/graph/meson.build                        |  25 +
>  app/graph/module_api.h                       |  31 +
>  app/graph/neigh.c                            | 364 ++++++++
>  app/graph/neigh.h                            |  17 +
>  app/graph/neigh_priv.h                       |  49 +
>  app/graph/route.h                            |  40 +
>  app/graph/route_priv.h                       |  44 +
>  app/graph/utils.c                            | 156 ++++
>  app/graph/utils.h                            |  14 +
>  app/meson.build                              |   1 +
>  doc/guides/rel_notes/release_23_11.rst       |   7 +
>  doc/guides/tools/graph.rst                   | 314 +++++++
>  doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 +++++
>  doc/guides/tools/index.rst                   |   1 +
>  38 files changed, 4883 insertions(+)
>  create mode 100644 app/graph/cli.c
>  create mode 100644 app/graph/cli.h
>  create mode 100644 app/graph/conn.c
>  create mode 100644 app/graph/conn.h
>  create mode 100644 app/graph/ethdev.c
>  create mode 100644 app/graph/ethdev.h
>  create mode 100644 app/graph/ethdev_priv.h  create mode 100644
> app/graph/ethdev_rx.c  create mode 100644 app/graph/ethdev_rx.h  create
> mode 100644 app/graph/ethdev_rx_priv.h  create mode 100644
> app/graph/examples/l3fwd.cli  create mode 100644
> app/graph/examples/l3fwd_pcap.cli  create mode 100644
> app/graph/graph.c  create mode 100644 app/graph/graph.h  create mode
> 100644 app/graph/graph_priv.h  create mode 100644 app/graph/ip4_route.c
> create mode 100644 app/graph/ip6_route.c  create mode 100644
> app/graph/l3fwd.c  create mode 100644 app/graph/l3fwd.h  create mode
> 100644 app/graph/main.c  create mode 100644 app/graph/mempool.c
> create mode 100644 app/graph/mempool.h  create mode 100644
> app/graph/mempool_priv.h  create mode 100644 app/graph/meson.build
> create mode 100644 app/graph/module_api.h  create mode 100644
> app/graph/neigh.c  create mode 100644 app/graph/neigh.h  create mode
> 100644 app/graph/neigh_priv.h  create mode 100644 app/graph/route.h
> create mode 100644 app/graph/route_priv.h  create mode 100644
> app/graph/utils.c  create mode 100644 app/graph/utils.h  create mode
> 100644 doc/guides/tools/graph.rst  create mode 100644
> doc/guides/tools/img/graph-usecase-l3fwd.svg
> 
> --
> 2.25.1


^ permalink raw reply	[flat|nested] 182+ messages in thread

* Re: [PATCH v11 00/12] add CLI based graph application
  2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
                                                     ` (12 preceding siblings ...)
  2023-10-23 13:03                                   ` [PATCH v11 00/12] add CLI based graph application Sunil Kumar Kori
@ 2023-11-03 16:44                                   ` Thomas Monjalon
  13 siblings, 0 replies; 182+ messages in thread
From: Thomas Monjalon @ 2023-11-03 16:44 UTC (permalink / raw)
  To: Sunil Kumar Kori, ndabilpuram; +Cc: dev, jerinj, rkudurumalla

19/10/2023 19:29, skori@marvell.com:
> From: Sunil Kumar Kori <skori@marvell.com>
> 
> In the continuation of following feedback
> https://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/
> this patch series adds dpdk-graph application to exercise various
> usecases using graph.

I've reworked the doc a bit,
and applied, thanks.




^ permalink raw reply	[flat|nested] 182+ messages in thread

end of thread, other threads:[~2023-11-03 16:45 UTC | newest]

Thread overview: 182+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-21  6:02 [PATCH 0/4] app: introduce testgraph application Vamsi Attunuru
2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
2023-05-29 17:29   ` Jerin Jacob
2023-05-31 12:36     ` [EXT] " Vamsi Krishna Attunuru
2023-04-21  6:02 ` [PATCH 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
2023-05-29 17:39   ` Jerin Jacob
2023-06-01  2:40     ` [EXT] " Vamsi Krishna Attunuru
2023-04-21  6:02 ` [PATCH 3/4] node: remove hardcoded node next details Vamsi Attunuru
2023-04-21  6:02 ` [PATCH 4/4] app: add testgraph application Vamsi Attunuru
2023-05-09  6:09   ` Jerin Jacob
2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
2023-05-30  8:16     ` Nithin Dabilpuram
2023-05-31 12:35       ` [EXT] " Vamsi Krishna Attunuru
2023-04-25 13:15   ` [PATCH v2 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 3/4] node: remove hardcoded node next details Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
2023-05-09  3:39       ` Vamsi Krishna Attunuru
2023-05-09  8:53         ` Sunil Kumar Kori
2023-05-09 12:24           ` Vamsi Krishna Attunuru
2023-05-12 11:08     ` Yan, Zhirun
2023-05-22  7:07       ` Vamsi Krishna Attunuru
2023-05-30  7:34         ` Jerin Jacob
2023-06-01  2:44           ` [EXT] " Vamsi Krishna Attunuru
2023-07-20 15:52             ` Rakesh Kudurumalla
2023-07-21  6:48               ` Jerin Jacob
2023-07-21  7:01                 ` Sunil Kumar Kori
2023-07-21  7:39                   ` Rakesh Kudurumalla
2023-09-08 11:00                     ` Sunil Kumar Kori
2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
2023-09-09  1:18       ` [EXT] " Nithin Kumar Dabilpuram
2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
2023-09-20  4:34           ` Jerin Jacob
2023-09-20  8:14             ` Bruce Richardson
2023-09-19 16:04         ` [PATCH v4 03/14] app/graph: add parser utility APIs skori
2023-09-19 16:04         ` [PATCH v4 04/14] app/graph: add mempool command line interfaces skori
2023-09-19 16:04         ` [PATCH v4 05/14] app/graph: add ethdev " skori
2023-09-19 16:04         ` [PATCH v4 06/14] app/graph: add ipv4_lookup " skori
2023-09-19 16:04         ` [PATCH v4 07/14] app/graph: add ipv6_lookup " skori
2023-09-19 16:04         ` [PATCH v4 08/14] app/graph: add neigh " skori
2023-09-19 16:04         ` [PATCH v4 09/14] app/graph: add ethdev_rx " skori
2023-09-19 16:04         ` [PATCH v4 10/14] app/graph: add graph " skori
2023-09-19 16:04         ` [PATCH v4 11/14] app/graph: add CLI option to enable graph stats skori
2023-09-19 16:04         ` [PATCH v4 12/14] app/graph: add l3fwd usecase skori
2023-09-19 16:04         ` [PATCH v4 13/14] doc: add graph application user guide skori
2023-09-20  4:28           ` Jerin Jacob
2023-09-19 16:04         ` [PATCH v4 14/14] maintainers: add maintainers for graph app skori
2023-09-20  4:40         ` [PATCH v4 01/14] app/graph: add application framework to read CLI Jerin Jacob
2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
2023-09-21 10:08           ` [PATCH v5 01/12] app/graph: add application framework to read CLI skori
2023-09-21 10:08           ` [PATCH v5 02/12] app/graph: add telnet connectivity framework skori
2023-09-21 10:08           ` [PATCH v5 03/12] app/graph: add parser utility APIs skori
2023-09-21 10:08           ` [PATCH v5 04/12] app/graph: add mempool command line interfaces skori
2023-09-21 10:08           ` [PATCH v5 05/12] app/graph: add ethdev " skori
2023-09-21 10:08           ` [PATCH v5 06/12] app/graph: add ipv4_lookup " skori
2023-09-21 10:08           ` [PATCH v5 07/12] app/graph: add ipv6_lookup " skori
2023-09-21 10:08           ` [PATCH v5 08/12] app/graph: add neigh " skori
2023-09-21 10:08           ` [PATCH v5 09/12] app/graph: add ethdev_rx " skori
2023-09-21 10:08           ` [PATCH v5 10/12] app/graph: add graph " skori
2023-09-21 10:08           ` [PATCH v5 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-21 10:08           ` [PATCH v5 12/12] app/graph: add l3fwd usecase skori
2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
2023-09-26 10:57               ` [PATCH v6 01/12] app/graph: add application framework to read CLI skori
2023-09-26 10:57               ` [PATCH v6 02/12] app/graph: add telnet connectivity framework skori
2023-09-26 10:57               ` [PATCH v6 03/12] app/graph: add parser utility APIs skori
2023-09-26 10:57               ` [PATCH v6 04/12] app/graph: add mempool command line interfaces skori
2023-09-26 10:57               ` [PATCH v6 05/12] app/graph: add ethdev " skori
2023-09-26 10:57               ` [PATCH v6 06/12] app/graph: add ipv4_lookup " skori
2023-09-26 10:57               ` [PATCH v6 07/12] app/graph: add ipv6_lookup " skori
2023-09-26 10:57               ` [PATCH v6 08/12] app/graph: add neigh " skori
2023-09-26 10:57               ` [PATCH v6 09/12] app/graph: add ethdev_rx " skori
2023-09-26 10:57               ` [PATCH v6 10/12] app/graph: add graph " skori
2023-09-26 10:57               ` [PATCH v6 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-26 10:57               ` [PATCH v6 12/12] app/graph: add l3fwd use case skori
2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
2023-09-27 11:54                   ` [PATCH v7 01/12] app/graph: add application framework to read CLI skori
2023-09-27 11:54                   ` [PATCH v7 02/12] app/graph: add telnet connectivity framework skori
2023-09-27 11:54                   ` [PATCH v7 03/12] app/graph: add parser utility APIs skori
2023-09-27 11:54                   ` [PATCH v7 04/12] app/graph: add mempool command line interfaces skori
2023-09-27 11:54                   ` [PATCH v7 05/12] app/graph: add ethdev " skori
2023-09-27 11:54                   ` [PATCH v7 06/12] app/graph: add ipv4_lookup " skori
2023-09-27 11:54                   ` [PATCH v7 07/12] app/graph: add ipv6_lookup " skori
2023-09-27 11:54                   ` [PATCH v7 08/12] app/graph: add neigh " skori
2023-09-27 11:54                   ` [PATCH v7 09/12] app/graph: add ethdev_rx " skori
2023-09-27 11:54                   ` [PATCH v7 10/12] app/graph: add graph " skori
2023-09-27 11:54                   ` [PATCH v7 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-27 11:54                   ` [PATCH v7 12/12] app/graph: add l3fwd use case skori
2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
2023-10-16  9:00                         ` Jerin Jacob
2023-10-17  6:19                           ` [EXT] " Sunil Kumar Kori
2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
2023-10-18  6:33                           ` [PATCH v9 01/12] app/graph: support application CLI framework skori
2023-10-18  6:33                           ` [PATCH v9 02/12] app/graph: support telnet connectivity framework skori
2023-10-18  6:33                           ` [PATCH v9 03/12] app/graph: support parser utility APIs skori
2023-10-18  6:33                           ` [PATCH v9 04/12] app/graph: support mempool command line interfaces skori
2023-10-18  6:33                           ` [PATCH v9 05/12] app/graph: support ethdev " skori
2023-10-18  6:33                           ` [PATCH v9 06/12] app/graph: support IPv4 lookup " skori
2023-10-18  6:33                           ` [PATCH v9 07/12] app/graph: support IPv6 " skori
2023-10-18  6:33                           ` [PATCH v9 08/12] app/graph: support neigh " skori
2023-10-18  6:33                           ` [PATCH v9 09/12] app/graph: support ethdev Rx " skori
2023-10-18  6:33                           ` [PATCH v9 10/12] app/graph: support graph " skori
2023-10-18  6:33                           ` [PATCH v9 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
2023-10-18 10:38                             ` Jerin Jacob
2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
2023-10-19 10:49                               ` [PATCH v10 01/12] app/graph: support application CLI framework skori
2023-10-19 10:49                               ` [PATCH v10 02/12] app/graph: support telnet connectivity framework skori
2023-10-19 10:49                               ` [PATCH v10 03/12] app/graph: support parser utility APIs skori
2023-10-19 10:49                               ` [PATCH v10 04/12] app/graph: support mempool command line interfaces skori
2023-10-19 10:49                               ` [PATCH v10 05/12] app/graph: support ethdev " skori
2023-10-19 10:49                               ` [PATCH v10 06/12] app/graph: support IPv4 lookup " skori
2023-10-19 10:49                               ` [PATCH v10 07/12] app/graph: support IPv6 " skori
2023-10-19 10:49                               ` [PATCH v10 08/12] app/graph: support neigh " skori
2023-10-19 10:49                               ` [PATCH v10 09/12] app/graph: support ethdev Rx " skori
2023-10-19 10:49                               ` [PATCH v10 10/12] app/graph: support graph " skori
2023-10-19 10:49                               ` [PATCH v10 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
2023-10-19 12:28                                 ` [EXT] " Jerin Jacob Kollanukkaran
2023-10-23  7:06                                   ` Nithin Dabilpuram
2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 02/12] app/graph: support telnet connectivity framework skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 03/12] app/graph: support parser utility APIs skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 04/12] app/graph: support mempool command line interfaces skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 05/12] app/graph: support ethdev " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 06/12] app/graph: support IPv4 lookup " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 07/12] app/graph: support IPv6 " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 08/12] app/graph: support neigh " skori
2023-10-23  7:05                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 09/12] app/graph: support ethdev Rx " skori
2023-10-23  7:05                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 10/12] app/graph: support graph " skori
2023-10-23  7:06                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-23  7:06                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 12/12] app/graph: support l3fwd use case skori
2023-10-23 13:03                                   ` [PATCH v11 00/12] add CLI based graph application Sunil Kumar Kori
2023-11-03 16:44                                   ` Thomas Monjalon
2023-09-29  9:58                       ` [PATCH v8 02/12] app/graph: add telnet connectivity framework skori
2023-10-16  9:04                         ` Jerin Jacob
2023-10-17  6:21                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 03/12] app/graph: add parser utility APIs skori
2023-10-16  9:05                         ` Jerin Jacob
2023-09-29  9:58                       ` [PATCH v8 04/12] app/graph: add mempool command line interfaces skori
2023-10-16  9:09                         ` Jerin Jacob
2023-10-17  6:22                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 05/12] app/graph: add ethdev " skori
2023-10-16 13:20                         ` Jerin Jacob
2023-10-16 14:10                           ` Bruce Richardson
2023-10-17  6:26                             ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 06/12] app/graph: add ipv4_lookup " skori
2023-10-16 15:47                         ` Jerin Jacob
2023-10-17  6:30                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 07/12] app/graph: add ipv6_lookup " skori
2023-09-29  9:58                       ` [PATCH v8 08/12] app/graph: add neigh " skori
2023-09-29  9:58                       ` [PATCH v8 09/12] app/graph: add ethdev_rx " skori
2023-09-29  9:58                       ` [PATCH v8 10/12] app/graph: add graph " skori
2023-09-29  9:58                       ` [PATCH v8 11/12] app/graph: add CLI option to enable graph stats skori
2023-10-16 15:55                         ` Jerin Jacob
2023-09-29  9:58                       ` [PATCH v8 12/12] app/graph: add l3fwd use case skori
2023-10-16 16:01                       ` [PATCH v8 00/12] add CLI based graph application Jerin Jacob
2023-10-16 16:27                         ` Stephen Hemminger
2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
2023-06-05 12:47       ` Nithin Dabilpuram
2023-06-12 19:32         ` Thomas Monjalon
2023-06-02 16:22     ` [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel Vamsi Attunuru
2023-06-05 12:50       ` Nithin Dabilpuram
2023-06-02 16:22     ` [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details Vamsi Attunuru
2023-06-05 12:51       ` Nithin Dabilpuram
2023-06-12 16:12     ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Krishna Attunuru
2023-06-12 19:31     ` Thomas Monjalon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).