From: Sunil Kumar Kori <sk...@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 <sk...@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkuduruma...@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 <sk...@marvell.com>
+M: Rakesh Kudurumalla <rkuduruma...@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

Reply via email to