[v3,1/1] app/graph: add example for different usecases
Checks
Commit Message
From: Sunil Kumar Kori <skori@marvell.com>
Current l3fwd-graph application only validates l3fwd use case.
To scale up this, new application will be added with a framework
to run as user's provided usecases.
Required configuration and use cases details are fetched via a
static .cli file which will be used to create a graph for
requested uscases.
Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
---
MAINTAINERS | 7 +
app/graph/cli.c | 208 ++++++
app/graph/cli.h | 48 ++
app/graph/cli_priv.h | 19 +
app/graph/conn.c | 284 +++++++++
app/graph/conn.h | 46 ++
app/graph/ethdev.c | 632 +++++++++++++++++++
app/graph/ethdev.h | 28 +
app/graph/ethdev_priv.h | 46 ++
app/graph/ethdev_rx.c | 139 ++++
app/graph/ethdev_rx.h | 32 +
app/graph/ethdev_rx_priv.h | 23 +
app/graph/examples/l3fwd.cli | 87 +++
app/graph/graph.c | 383 +++++++++++
app/graph/graph.h | 11 +
app/graph/graph_priv.h | 32 +
app/graph/ip4_route.c | 146 +++++
app/graph/ip6_route.c | 154 +++++
app/graph/l3fwd.c | 152 +++++
app/graph/l3fwd.h | 11 +
app/graph/main.c | 201 ++++++
app/graph/mempool.c | 134 ++++
app/graph/mempool.h | 18 +
app/graph/mempool_priv.h | 16 +
app/graph/meson.build | 25 +
app/graph/module_api.h | 33 +
app/graph/neigh.c | 269 ++++++++
app/graph/neigh.h | 11 +
app/graph/neigh_priv.h | 22 +
app/graph/route.h | 30 +
app/graph/utils.c | 155 +++++
app/graph/utils.h | 14 +
app/meson.build | 1 +
doc/guides/tools/graph.rst | 171 +++++
doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++
doc/guides/tools/index.rst | 1 +
36 files changed, 3799 insertions(+)
create mode 100644 app/graph/cli.c
create mode 100644 app/graph/cli.h
create mode 100644 app/graph/cli_priv.h
create mode 100644 app/graph/conn.c
create mode 100644 app/graph/conn.h
create mode 100644 app/graph/ethdev.c
create mode 100644 app/graph/ethdev.h
create mode 100644 app/graph/ethdev_priv.h
create mode 100644 app/graph/ethdev_rx.c
create mode 100644 app/graph/ethdev_rx.h
create mode 100644 app/graph/ethdev_rx_priv.h
create mode 100644 app/graph/examples/l3fwd.cli
create mode 100644 app/graph/graph.c
create mode 100644 app/graph/graph.h
create mode 100644 app/graph/graph_priv.h
create mode 100644 app/graph/ip4_route.c
create mode 100644 app/graph/ip6_route.c
create mode 100644 app/graph/l3fwd.c
create mode 100644 app/graph/l3fwd.h
create mode 100644 app/graph/main.c
create mode 100644 app/graph/mempool.c
create mode 100644 app/graph/mempool.h
create mode 100644 app/graph/mempool_priv.h
create mode 100644 app/graph/meson.build
create mode 100644 app/graph/module_api.h
create mode 100644 app/graph/neigh.c
create mode 100644 app/graph/neigh.h
create mode 100644 app/graph/neigh_priv.h
create mode 100644 app/graph/route.h
create mode 100644 app/graph/utils.c
create mode 100644 app/graph/utils.h
create mode 100644 doc/guides/tools/graph.rst
create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg
Comments
Please see inline.
> -----Original Message-----
> From: skori@marvell.com <skori@marvell.com>
> Sent: Friday, September 8, 2023 4:19 PM
> To: Thomas Monjalon <thomas@monjalon.net>; Sunil Kumar Kori <skori@marvell.com>;
> Rakesh Kudurumalla <rkudurumalla@marvell.com>
> Cc: dev@dpdk.org
> Subject: [EXT] [PATCH v3 1/1] app/graph: add example for different usecases
>
> External Email
>
> ----------------------------------------------------------------------
> From: Sunil Kumar Kori <skori@marvell.com>
>
> Current l3fwd-graph application only validates l3fwd use case.
> To scale up this, new application will be added with a framework
> to run as user's provided usecases.
>
> Required configuration and use cases details are fetched via a
> static .cli file which will be used to create a graph for
> requested uscases.
>
> Signed-off-by: Sunil Kumar Kori <skori@marvell.com>
> Signed-off-by: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> ---
> MAINTAINERS | 7 +
> app/graph/cli.c | 208 ++++++
> app/graph/cli.h | 48 ++
> app/graph/cli_priv.h | 19 +
> app/graph/conn.c | 284 +++++++++
> app/graph/conn.h | 46 ++
> app/graph/ethdev.c | 632 +++++++++++++++++++
> app/graph/ethdev.h | 28 +
> app/graph/ethdev_priv.h | 46 ++
> app/graph/ethdev_rx.c | 139 ++++
> app/graph/ethdev_rx.h | 32 +
> app/graph/ethdev_rx_priv.h | 23 +
> app/graph/examples/l3fwd.cli | 87 +++
> app/graph/graph.c | 383 +++++++++++
> app/graph/graph.h | 11 +
> app/graph/graph_priv.h | 32 +
> app/graph/ip4_route.c | 146 +++++
> app/graph/ip6_route.c | 154 +++++
> app/graph/l3fwd.c | 152 +++++
> app/graph/l3fwd.h | 11 +
> app/graph/main.c | 201 ++++++
> app/graph/mempool.c | 134 ++++
> app/graph/mempool.h | 18 +
> app/graph/mempool_priv.h | 16 +
> app/graph/meson.build | 25 +
> app/graph/module_api.h | 33 +
> app/graph/neigh.c | 269 ++++++++
> app/graph/neigh.h | 11 +
> app/graph/neigh_priv.h | 22 +
> app/graph/route.h | 30 +
> app/graph/utils.c | 155 +++++
> app/graph/utils.h | 14 +
> app/meson.build | 1 +
> doc/guides/tools/graph.rst | 171 +++++
> doc/guides/tools/img/graph-usecase-l3fwd.svg | 210 ++++++
> doc/guides/tools/index.rst | 1 +
> 36 files changed, 3799 insertions(+)
> create mode 100644 app/graph/cli.c
> create mode 100644 app/graph/cli.h
> create mode 100644 app/graph/cli_priv.h
> create mode 100644 app/graph/conn.c
> create mode 100644 app/graph/conn.h
> create mode 100644 app/graph/ethdev.c
> create mode 100644 app/graph/ethdev.h
> create mode 100644 app/graph/ethdev_priv.h
> create mode 100644 app/graph/ethdev_rx.c
> create mode 100644 app/graph/ethdev_rx.h
> create mode 100644 app/graph/ethdev_rx_priv.h
> create mode 100644 app/graph/examples/l3fwd.cli
> create mode 100644 app/graph/graph.c
> create mode 100644 app/graph/graph.h
> create mode 100644 app/graph/graph_priv.h
> create mode 100644 app/graph/ip4_route.c
> create mode 100644 app/graph/ip6_route.c
> create mode 100644 app/graph/l3fwd.c
> create mode 100644 app/graph/l3fwd.h
> create mode 100644 app/graph/main.c
> create mode 100644 app/graph/mempool.c
> create mode 100644 app/graph/mempool.h
> create mode 100644 app/graph/mempool_priv.h
> create mode 100644 app/graph/meson.build
> create mode 100644 app/graph/module_api.h
> create mode 100644 app/graph/neigh.c
> create mode 100644 app/graph/neigh.h
> create mode 100644 app/graph/neigh_priv.h
> create mode 100644 app/graph/route.h
> create mode 100644 app/graph/utils.c
> create mode 100644 app/graph/utils.h
> create mode 100644 doc/guides/tools/graph.rst
> create mode 100644 doc/guides/tools/img/graph-usecase-l3fwd.svg
>
[Nithin] Split to multiple smaller patches
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 698608cdb2..7f149bd060 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1806,6 +1806,13 @@ F: dts/
> F: devtools/dts-check-format.sh
> F: doc/guides/tools/dts.rst
>
> +Graph application
> +M: Sunil Kumar Kori <skori@marvell.com>
> +M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
> +F: app/graph/
> +F: doc/guides/tools/graph.rst
> +F: doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
>
> Other Example Applications
> --------------------------
> diff --git a/app/graph/cli.c b/app/graph/cli.c
> new file mode 100644
> index 0000000000..237fa8008f
> --- /dev/null
> +++ b/app/graph/cli.c
> @@ -0,0 +1,208 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_malloc.h>
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "cli_priv.h"
> +#include "module_api.h"
> +
> +#define CMD_MAX_TOKENS 256
> +#define MAX_LINE_SIZE 2048
> +
> +static struct cli_node_head module_list = STAILQ_HEAD_INITIALIZER(module_list);
> +
> +#define PARSE_DELIMITER " \f\n\r\t\v"
> +
> +static int
> +tokenize_string_parse(char *string, char *tokens[], uint32_t *n_tokens)
> +{
> + uint32_t i;
> +
> + if ((string == NULL) ||
> + (tokens == NULL) ||
> + (*n_tokens < 1))
> + return -EINVAL;
> +
> + for (i = 0; i < *n_tokens; i++) {
> + tokens[i] = strtok_r(string, PARSE_DELIMITER, &string);
> + if (tokens[i] == NULL)
> + break;
> + }
> +
> + if ((i == *n_tokens) && strtok_r(string, PARSE_DELIMITER, &string))
> + return -E2BIG;
> +
> + *n_tokens = i;
> + return 0;
> +}
> +
> +static int
> +is_comment(char *in)
> +{
> + if ((strlen(in) && index("!#%;", in[0])) ||
> + (strncmp(in, "//", 2) == 0) ||
> + (strncmp(in, "--", 2) == 0))
> + return 1;
> +
> + return 0;
> +}
> +
> +static bool
> +module_list_has_cmd_registered(const char *cmd)
> +{
> + struct cli_node *node;
> +
> + STAILQ_FOREACH(node, &module_list, next) {
> + if (strcmp(node->cmd, cmd) == 0) {
> + rte_errno = EEXIST;
> + return 1;
> + }
> + }
> + return 0;
> +}
> +
> +void
> +cli_module_register(const struct cli_module *module)
> +{
> + struct cli_node *node;
> +
> + /* Check sanity */
> + if (module == NULL || module->process == NULL) {
> + rte_errno = EINVAL;
> + return;
> + }
> +
> + /* Check for duplicate name */
> + if (module_list_has_cmd_registered(module->cmd)) {
> + printf("module %s is already registered\n", module->cmd);
> + return;
> + }
> +
> + node = malloc(sizeof(struct cli_node));
> + if (node == NULL) {
> + rte_errno = ENOMEM;
> + return;
> + }
> +
> + /* Initialize the node */
> + if (rte_strscpy(node->cmd, module->cmd, APP_CLI_CMD_NAME_SIZE) < 0) {
> + free(node);
> + return;
> + }
> + node->process = module->process;
> + node->usage = module->usage;
> +
> + /* Add the node at tail */
> + STAILQ_INSERT_TAIL(&module_list, node, next);
> +}
> +
> +void
> +cli_process(char *in, char *out, size_t out_size, void *obj)
> +{
> + char *tokens[CMD_MAX_TOKENS];
> + struct cli_node *node;
> + uint32_t n_tokens;
> + int rc;
> +
> + if (is_comment(in))
> + return;
> +
> + n_tokens = RTE_DIM(tokens);
> + rc = tokenize_string_parse(in, tokens, &n_tokens);
> + if (rc) {
> + snprintf(out, out_size, MSG_ARG_TOO_MANY, "");
> + return;
> + }
> +
> + if (n_tokens == 0)
> + return;
> +
> + if ((n_tokens == 1) && strcmp(tokens[0], "help") == 0) {
> + STAILQ_FOREACH(node, &module_list, next) {
> + node->usage(tokens, n_tokens, out, out_size, obj);
> + }
> + return;
> + }
> +
> + if ((n_tokens >= 2) && strcmp(tokens[0], "help") == 0) {
> + STAILQ_FOREACH(node, &module_list, next) {
> + if (strcmp(node->cmd, tokens[1]) == 0) {
> + node->usage(tokens, n_tokens, out, out_size, obj);
> + return;
> + }
> + }
> + snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> + return;
> + }
> +
> + STAILQ_FOREACH(node, &module_list, next) {
> + if (strcmp(node->cmd, tokens[0]) == 0) {
> + rc = node->process(tokens, n_tokens, out, out_size, obj);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> + return;
> + }
> + }
> +
> + snprintf(out, out_size, MSG_CMD_UNKNOWN, tokens[0]);
> +}
> +
> +int
> +cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max, void *obj)
> +{
> + char *msg_in = NULL, *msg_out = NULL;
> + FILE *f = NULL;
> +
> + /* Check input arguments */
> + if ((file_name == NULL) || (strlen(file_name) == 0) || (msg_in_len_max == 0) ||
> + (msg_out_len_max == 0))
> + return -EINVAL;
> +
> + msg_in = malloc(msg_in_len_max + 1);
> + msg_out = malloc(msg_out_len_max + 1);
> + if ((msg_in == NULL) || (msg_out == NULL)) {
> + free(msg_out);
> + free(msg_in);
> + return -ENOMEM;
> + }
> +
> + /* Open input file */
> + f = fopen(file_name, "r");
> + if (f == NULL) {
> + free(msg_out);
> + free(msg_in);
> + return -EIO;
> + }
> +
> + /* Read file */
> + while (1) {
> + if (fgets(msg_in, msg_in_len_max + 1, f) == NULL)
> + break;
> +
> + msg_out[0] = 0;
> +
> + cli_process(msg_in, msg_out, msg_out_len_max, obj);
> +
> + if (strlen(msg_out))
> + printf("%s", msg_out);
> + }
> +
> + /* Close file */
> + fclose(f);
> + free(msg_out);
> + free(msg_in);
> + return 0;
> +}
> diff --git a/app/graph/cli.h b/app/graph/cli.h
> new file mode 100644
> index 0000000000..2bd89f3d1f
> --- /dev/null
> +++ b/app/graph/cli.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_H
> +#define APP_GRAPH_CLI_H
> +
> +/* Macros */
> +#define MSG_OUT_OF_MEMORY "Not enough memory.\n"
> +#define MSG_CMD_UNKNOWN "Unknown command \"%s\".\n"
> +#define MSG_CMD_UNIMPLEM "Command \"%s\" not implemented.\n"
> +#define MSG_ARG_NOT_ENOUGH "Not enough arguments for command \"%s\".\n"
> +#define MSG_ARG_TOO_MANY "Too many arguments for command \"%s\".\n"
> +#define MSG_ARG_MISMATCH "Wrong number of arguments for command \"%s\".\n"
> +#define MSG_ARG_NOT_FOUND "Argument \"%s\" not found.\n"
> +#define MSG_ARG_INVALID "Invalid value for argument \"%s\".\n"
> +#define MSG_FILE_ERR "Error in file \"%s\" at line %u.\n"
> +#define MSG_FILE_NOT_ENOUGH "Not enough rules in file \"%s\".\n"
> +#define MSG_CMD_FAIL "Command \"%s\" failed.\n"
> +
> +#define APP_CLI_CMD_NAME_SIZE 64
> +
> +/* Typedefs */
> +typedef int (*cli_module_t)(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> + void *obj);
> +
> +/* Structures */
> +struct cli_module {
> + char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command to be
> registered. */
> + cli_module_t process; /**< Command process function. */
> + cli_module_t usage; /**< Help command process function. */
> +};
> +
> +/* APIs */
> +void cli_module_register(const struct cli_module *module);
> +
> +#define CLI_REGISTER(module) \
> + RTE_INIT(cli_register_##module) \
> + { \
> + cli_module_register(&module); \
> + }
> +
> +void cli_process(char *in, char *out, size_t out_size, void *arg);
> +
> +int cli_script_process(const char *file_name, size_t msg_in_len_max, size_t
> msg_out_len_max,
> + void *arg);
> +
> +#endif
> diff --git a/app/graph/cli_priv.h b/app/graph/cli_priv.h
> new file mode 100644
> index 0000000000..9ecc89c353
> --- /dev/null
> +++ b/app/graph/cli_priv.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CLI_PRIV_H
> +#define APP_GRAPH_CLI_PRIV_H
> +
> +#include "cli.h"
> +
> +struct cli_node {
> + STAILQ_ENTRY(cli_node) next; /**< Next node in the list. */
> + char cmd[APP_CLI_CMD_NAME_SIZE]; /**< Name of the command. */
> + cli_module_t process; /**< Command process function. */
> + cli_module_t usage; /**< Help command process function. */
> +};
> +
> +STAILQ_HEAD(cli_node_head, cli_node);
> +
> +#endif
> diff --git a/app/graph/conn.c b/app/graph/conn.c
> new file mode 100644
> index 0000000000..dabc8deca2
> --- /dev/null
> +++ b/app/graph/conn.c
> @@ -0,0 +1,284 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <netinet/in.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#include "module_api.h"
> +
> +#define MSG_CMD_TOO_LONG "Command too long."
> +
> +static int
> +data_event_handle(struct conn *conn, int fd_client)
> +{
> + ssize_t len, i, rc = 0;
> +
> + /* Read input message */
> + len = read(fd_client, conn->buf, conn->buf_size);
> + if (len == -1) {
> + if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> + return 0;
> +
> + return -1;
> + }
> +
> + if (len == 0)
> + return rc;
> +
> + /* Handle input messages */
> + for (i = 0; i < len; i++) {
> + if (conn->buf[i] == '\n') {
> + size_t n;
> +
> + conn->msg_in[conn->msg_in_len] = 0;
> + conn->msg_out[0] = 0;
> +
> + conn->msg_handle(conn->msg_in, conn->msg_out, conn-
> >msg_out_len_max,
> + conn->msg_handle_arg);
> +
> + n = strlen(conn->msg_out);
> + if (n) {
> + rc = write(fd_client, conn->msg_out, n);
> + if (rc == -1)
> + goto exit;
> + }
> +
> + conn->msg_in_len = 0;
> + } else if (conn->msg_in_len < conn->msg_in_len_max) {
> + conn->msg_in[conn->msg_in_len] = conn->buf[i];
> + conn->msg_in_len++;
> + } else {
> + rc = write(fd_client, MSG_CMD_TOO_LONG,
> strlen(MSG_CMD_TOO_LONG));
> + if (rc == -1)
> + goto exit;
> +
> + conn->msg_in_len = 0;
> + }
> + }
> +
> + /* Write prompt */
> + rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> + rc = (rc == -1) ? -1 : 0;
> +
> +exit:
> + return rc;
> +}
> +
> +static int
> +control_event_handle(struct conn *conn, int fd_client)
> +{
> + int rc;
> +
> + rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_DEL, fd_client, NULL);
> + if (rc == -1)
> + goto exit;
> +
> + rc = close(fd_client);
> + if (rc == -1)
> + goto exit;
> +
> + rc = 0;
> +
> +exit:
> + return rc;
> +}
> +
> +struct conn *
> +conn_init(struct conn_params *p)
> +{
> + int fd_server, fd_client_group, rc;
> + struct sockaddr_in server_address;
> + struct conn *conn = NULL;
> +
> + memset(&server_address, 0, sizeof(server_address));
> +
> + /* Check input arguments */
> + if ((p == NULL) || (p->welcome == NULL) || (p->prompt == NULL) || (p->addr ==
> NULL) ||
> + (p->buf_size == 0) || (p->msg_in_len_max == 0) || (p->msg_out_len_max == 0)
> ||
> + (p->msg_handle == NULL))
> + goto exit;
> +
> + rc = inet_aton(p->addr, &server_address.sin_addr);
> + if (rc == 0)
> + goto exit;
> +
> + /* Memory allocation */
> + conn = calloc(1, sizeof(struct conn));
> + if (conn == NULL)
> + goto exit;
> +
> + conn->welcome = calloc(1, CONN_WELCOME_LEN_MAX + 1);
> + conn->prompt = calloc(1, CONN_PROMPT_LEN_MAX + 1);
> + conn->buf = calloc(1, p->buf_size);
> + conn->msg_in = calloc(1, p->msg_in_len_max + 1);
> + conn->msg_out = calloc(1, p->msg_out_len_max + 1);
> +
> + if ((conn->welcome == NULL) || (conn->prompt == NULL) || (conn->buf == NULL)
> ||
> + (conn->msg_in == NULL) || (conn->msg_out == NULL)) {
> + conn_free(conn);
> + conn = NULL;
> + goto exit;
> + }
> +
> + /* Server socket */
> + server_address.sin_family = AF_INET;
> + server_address.sin_port = htons(p->port);
> +
> + fd_server = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
> + if (fd_server == -1) {
> + conn_free(conn);
> + conn = NULL;
> + goto exit;
> + }
> +
> + rc = bind(fd_server, (struct sockaddr *)&server_address, sizeof(server_address));
> + if (rc == -1) {
> + conn_free(conn);
> + close(fd_server);
> + conn = NULL;
> + goto exit;
> + }
> +
> + rc = listen(fd_server, 16);
> + if (rc == -1) {
> + conn_free(conn);
> + close(fd_server);
> + conn = NULL;
> + goto exit;
> + }
> +
> + /* Client group */
> + fd_client_group = epoll_create(1);
> + if (fd_client_group == -1) {
> + conn_free(conn);
> + close(fd_server);
> + conn = NULL;
> + goto exit;
> + }
> +
> + /* Fill in */
> + strncpy(conn->welcome, p->welcome, CONN_WELCOME_LEN_MAX);
> + strncpy(conn->prompt, p->prompt, CONN_PROMPT_LEN_MAX);
> + conn->buf_size = p->buf_size;
> + conn->msg_in_len_max = p->msg_in_len_max;
> + conn->msg_out_len_max = p->msg_out_len_max;
> + conn->msg_in_len = 0;
> + conn->fd_server = fd_server;
> + conn->fd_client_group = fd_client_group;
> + conn->msg_handle = p->msg_handle;
> + conn->msg_handle_arg = p->msg_handle_arg;
> +
> +exit:
> + return conn;
> +}
> +
> +void
> +conn_free(struct conn *conn)
> +{
> + if (conn == NULL)
> + return;
> +
> + if (conn->fd_client_group)
> + close(conn->fd_client_group);
> +
> + if (conn->fd_server)
> + close(conn->fd_server);
> +
> + free(conn->msg_out);
> + free(conn->msg_in);
> + free(conn->prompt);
> + free(conn->welcome);
> + free(conn);
> +}
> +
> +int
> +conn_req_poll(struct conn *conn)
> +{
> + struct sockaddr_in client_address;
> + socklen_t client_address_length;
> + struct epoll_event event;
> + int fd_client, rc;
> +
> + /* Check input arguments */
> + if (conn == NULL)
> + return -1;
> +
> + /* Server socket */
> + client_address_length = sizeof(client_address);
> + fd_client = accept4(conn->fd_server, (struct sockaddr *)&client_address,
> + &client_address_length, SOCK_NONBLOCK);
> + if (fd_client == -1) {
> + if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
> + return 0;
> +
> + return -1;
> + }
> +
> + /* Client group */
> + event.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP;
> + event.data.fd = fd_client;
> +
> + rc = epoll_ctl(conn->fd_client_group, EPOLL_CTL_ADD, fd_client, &event);
> + if (rc == -1) {
> + close(fd_client);
> + goto exit;
> + }
> +
> + /* Client */
> + rc = write(fd_client, conn->welcome, strlen(conn->welcome));
> + if (rc == -1) {
> + close(fd_client);
> + goto exit;
> + }
> +
> + rc = write(fd_client, conn->prompt, strlen(conn->prompt));
> + if (rc == -1) {
> + close(fd_client);
> + goto exit;
> + }
> +
> + rc = 0;
> +
> +exit:
> + return rc;
> +}
> +
> +int
> +conn_msg_poll(struct conn *conn)
> +{
> + int fd_client, rc, rc_data = 0, rc_control = 0;
> + struct epoll_event event;
> +
> + /* Check input arguments */
> + if (conn == NULL)
> + return -1;
> +
> + /* Client group */
> + rc = epoll_wait(conn->fd_client_group, &event, 1, 0);
> + if ((rc == -1) || rc == 0)
> + return rc;
> +
> + fd_client = event.data.fd;
> +
> + /* Data available */
> + if (event.events & EPOLLIN)
> + rc_data = data_event_handle(conn, fd_client);
> +
> + /* Control events */
> + if (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP))
> + rc_control = control_event_handle(conn, fd_client);
> +
> + if (rc_data || rc_control)
> + return -1;
> +
> + return 0;
> +}
> diff --git a/app/graph/conn.h b/app/graph/conn.h
> new file mode 100644
> index 0000000000..770964cf4c
> --- /dev/null
> +++ b/app/graph/conn.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_CONN_H
> +#define APP_GRAPH_CONN_H
> +
> +#define CONN_WELCOME_LEN_MAX 1024
> +#define CONN_PROMPT_LEN_MAX 16
> +
> +typedef void (*conn_msg_handle_t)(char *msg_in, char *msg_out, size_t
> msg_out_len_max, void *arg);
> +
> +struct conn {
> + char *welcome;
> + char *prompt;
> + char *buf;
> + char *msg_in;
> + char *msg_out;
> + size_t buf_size;
> + size_t msg_in_len_max;
> + size_t msg_out_len_max;
> + size_t msg_in_len;
> + int fd_server;
> + int fd_client_group;
> + conn_msg_handle_t msg_handle;
> + void *msg_handle_arg;
> +};
> +
> +struct conn_params {
> + const char *welcome;
> + const char *prompt;
> + const char *addr;
> + uint16_t port;
> + size_t buf_size;
> + size_t msg_in_len_max;
> + size_t msg_out_len_max;
> + conn_msg_handle_t msg_handle;
> + void *msg_handle_arg;
> +};
> +
> +struct conn *conn_init(struct conn_params *p);
> +void conn_free(struct conn *conn);
> +int conn_req_poll(struct conn *conn);
> +int conn_msg_poll(struct conn *conn);
> +
> +#endif
> diff --git a/app/graph/ethdev.c b/app/graph/ethdev.c
> new file mode 100644
> index 0000000000..840a8ca42f
> --- /dev/null
> +++ b/app/graph/ethdev.c
> @@ -0,0 +1,632 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_bitops.h>
> +#include <rte_ethdev.h>
> +#include <rte_mempool.h>
> +
> +#include "ethdev_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_mtu_help[] = "ethdev <ethdev_name> mtu <mtu_sz>";
> +
> +static const char
> +cmd_ethdev_prom_mode_help[] = "ethdev <ethdev_name> promiscuous <on/off>";
> +
> +static const char
> +cmd_ethdev_help[] = "ethdev <ethdev_name> rxq <n_queues> txq <n_queues>
> <mempool_name> "
> + "[mtu <mtu_sz>]";
> +static const char
> +cmd_ethdev_show_help[] = "ethdev <ethdev_name> show";
> +
> +static const char
> +cmd_ethdev_ip4_addr_help[] = "ethdev <ethdev_name> ip4 addr add <ip> netmask
> <mask>";
> +
> +static const char
> +cmd_ethdev_ip6_addr_help[] = "ethdev <ethdev_name> ip6 addr add <ip> netmask
> <mask>";
> +
> +static struct rte_eth_conf port_conf_default = {
> + .link_speeds = 0,
> + .rxmode = {
> + .mq_mode = RTE_ETH_MQ_RX_NONE,
> + .mtu = 9000 - (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN), /* Jumbo
> frame MTU */
> + },
> + .rx_adv_conf = {
> + .rss_conf = {
> + .rss_key = NULL,
> + .rss_key_len = 40,
> + .rss_hf = 0,
> + },
> + },
> + .txmode = {
> + .mq_mode = RTE_ETH_MQ_TX_NONE,
> + },
> + .lpbk_mode = 0,
> +};
> +
> +uint32_t enabled_port_mask;
> +struct ethdev port_list[RTE_MAX_ETHPORTS];
> +
> +void *
> +ethdev_mempool_list_by_portid(uint16_t portid)
> +{
> + if (portid >= RTE_MAX_ETHPORTS)
> + return NULL;
> +
> + return &port_list[portid].config.rx.mp;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip4(uint32_t ip)
> +{
> + int portid = -EINVAL;
> + int i;
> +
> + for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> + if ((port_list[i].ip4_addr.ip & route4[i].netmask) == (ip &
> route4[i].netmask))
> + break;
> + }
> +
> + if (i == RTE_MAX_ETHPORTS)
> + return portid;
> +
> + return port_list[i].config.port_id;
> +}
> +
> +int16_t
> +ethdev_portid_by_ip6(uint8_t *ip)
> +{
> + int portid = -EINVAL;
> + int i, j;
> +
> + for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> + for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> + if ((port_list[i].ip6_addr.ip[j] & route6[i].mask[j]) !=
> + (ip[j] & route6[i].mask[j]))
> + break;
> + }
> +
> + if (j == ETHDEV_IPV6_ADDR_LEN)
> + break;
> + }
> +
> + if (i == RTE_MAX_ETHPORTS)
> + return portid;
> +
> + return port_list[i].config.port_id;
> +}
> +
> +void
> +ethdev_stop(void)
> +{
> + uint16_t portid;
> + int rc;
> +
> + RTE_ETH_FOREACH_DEV(portid) {
> + if ((enabled_port_mask & (1 << portid)) == 0)
> + continue;
> + printf("Closing port %d...", portid);
> + rc = rte_eth_dev_stop(portid);
> + if (rc != 0)
> + printf("Failed to stop port %u: %s\n",
> + portid, rte_strerror(-rc));
> + rte_eth_dev_close(portid);
> + printf(" Done\n");
> + }
> +
> + /* clean up the EAL */
> + rte_eal_cleanup();
> + printf("Bye...\n");
> +}
> +
> +void
> +ethdev_start(void)
> +{
> + uint16_t portid;
> + int rc;
> +
> + RTE_ETH_FOREACH_DEV(portid)
> + {
> + if ((enabled_port_mask & (1 << portid)) == 0)
> + continue;
> +
> + rc = rte_eth_dev_start(portid);
> + if (rc < 0)
> + rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n",
> rc, portid);
> + }
> +}
> +
> +
> +static int
> +ethdev_show(const char *name, char **out, size_t *out_size)
> +{
> + uint16_t mtu = 0, port_id = 0;
> + struct rte_eth_dev_info info;
> + struct rte_eth_stats stats;
> + struct rte_ether_addr addr;
> + struct rte_eth_link link;
> + uint32_t length;
> + int rc;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &port_id);
> + if (rc < 0)
> + return rc;
> +
> + rte_eth_dev_info_get(port_id, &info);
> + rte_eth_stats_get(port_id, &stats);
> + rte_eth_macaddr_get(port_id, &addr);
> + rte_eth_link_get(port_id, &link);
> + rte_eth_dev_get_mtu(port_id, &mtu);
> +
> + snprintf(*out, *out_size,
> + "%s: flags=<%s> mtu %u\n"
> + "\tether " RTE_ETHER_ADDR_PRT_FMT " rxqueues %u txqueues %u\n"
> + "\tport# %u speed %s\n"
> + "\tRX packets %" PRIu64" bytes %" PRIu64"\n"
> + "\tRX errors %" PRIu64" missed %" PRIu64" no-mbuf %" PRIu64"\n"
> + "\tTX packets %" PRIu64" bytes %" PRIu64"\n"
> + "\tTX errors %" PRIu64"\n\n",
> + name,
> + link.link_status ? "UP" : "DOWN",
> + mtu,
> + RTE_ETHER_ADDR_BYTES(&addr),
> + info.nb_rx_queues,
> + info.nb_tx_queues,
> + port_id,
> + rte_eth_link_speed_to_str(link.link_speed),
> + stats.ipackets,
> + stats.ibytes,
> + stats.ierrors,
> + stats.imissed,
> + stats.rx_nombuf,
> + stats.opackets,
> + stats.obytes,
> + stats.oerrors);
> +
> + length = strlen(*out);
> + *out_size -= length;
> + *out += length;
> + return 0;
> +}
> +
> +static int
> +ethdev_ip4_addr_add(const char *name, struct ipv4_addr_config *config)
> +{
> + uint16_t portid = 0;
> + int rc;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &portid);
> + if (rc < 0)
> + return rc;
> +
> + port_list[portid].ip4_addr.ip = config->ip;
> + port_list[portid].ip4_addr.mask = config->mask;
> + return 0;
> +}
> +
> +static int
> +ethdev_ip6_addr_add(const char *name, struct ipv6_addr_config *config)
> +{
> + uint16_t portid = 0;
> + int rc, i;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &portid);
> + if (rc < 0)
> + return rc;
> +
> + for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> + port_list[portid].ip6_addr.ip[i] = config->ip[i];
> + port_list[portid].ip6_addr.mask[i] = config->mask[i];
> + }
> +
> + return 0;
> +}
> +
> +static int
> +ethdev_prom_mode_config(const char *name, bool enable)
> +{
> + uint16_t portid = 0;
> + int rc;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &portid);
> + if (rc < 0)
> + return rc;
> +
> + if (enable)
> + rc = rte_eth_promiscuous_enable(portid);
> + else
> + rc = rte_eth_promiscuous_disable(portid);
> +
> + if (rc < 0)
> + return rc;
> +
> + port_list[portid].config.promiscuous = enable;
> + return 0;
> +}
> +
> +static int
> +ethdev_mtu_config(const char *name, uint32_t mtu)
> +{
> + uint16_t portid = 0;
> + int rc;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &portid);
> + if (rc < 0)
> + return rc;
> +
> + rc = rte_eth_dev_set_mtu(portid, mtu);
> + if (rc < 0)
> + return rc;
> +
> + port_list[portid].config.mtu = mtu;
> + return 0;
> +}
> +
> +static int
> +ethdev_process(const char *name, struct ethdev_config *params)
> +{
> + struct rte_eth_dev_info port_info;
> + struct rte_eth_conf port_conf;
> + struct ethdev_rss_config *rss;
> + struct rte_mempool *mempool;
> + struct rte_ether_addr smac;
> + int numa_node, rc;
> + uint16_t port_id = 0;
> + uint32_t i;
> +
> + /* Check input params */
> + if (!name || !name[0] || !params || !params->rx.n_queues || !params-
> >rx.queue_size ||
> + !params->tx.n_queues || !params->tx.queue_size)
> + return -EINVAL;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &port_id);
> + if (rc)
> + return -EINVAL;
> +
> + rc = rte_eth_dev_info_get(port_id, &port_info);
> + if (rc)
> + return -EINVAL;
> +
> + mempool = rte_mempool_lookup(params->rx.mempool_name);
> + if (!mempool)
> + return -EINVAL;
> +
> + params->rx.mp = mempool;
> +
> + rss = params->rx.rss;
> + if (rss) {
> + if (!port_info.reta_size || port_info.reta_size >
> RTE_ETH_RSS_RETA_SIZE_512)
> + return -EINVAL;
> +
> + if (!rss->n_queues || rss->n_queues >= ETHDEV_RXQ_RSS_MAX)
> + return -EINVAL;
> +
> + for (i = 0; i < rss->n_queues; i++)
> + if (rss->queue_id[i] >= port_info.max_rx_queues)
> + return -EINVAL;
> + }
> +
> + /* Port */
> + memcpy(&port_conf, &port_conf_default, sizeof(struct rte_eth_conf));
> + if (rss) {
> + uint64_t rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP |
> RTE_ETH_RSS_UDP;
> +
> + port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
> + port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf &
> port_info.flow_type_rss_offloads;
> + }
> +
> + numa_node = rte_eth_dev_socket_id(port_id);
> + if (numa_node == SOCKET_ID_ANY)
> + numa_node = 0;
> +
> + if (params->mtu)
> + port_conf.rxmode.mtu = params->mtu;
> +
> + rc = rte_eth_dev_configure(port_id, params->rx.n_queues, params->tx.n_queues,
> + &port_conf);
> + if (rc < 0)
> + return -EINVAL;
> +
> + rc = rte_eth_macaddr_get(port_id, &smac);
> + if (rc < 0)
> + return -EINVAL;
> +
> + printf("Port_id = %d srcmac = %x:%x:%x:%x:%x:%x\n", port_id,
> + smac.addr_bytes[0], smac.addr_bytes[1],
> + smac.addr_bytes[2], smac.addr_bytes[3],
> + smac.addr_bytes[4], smac.addr_bytes[5]);
> +
> + /* Port RX */
> + for (i = 0; i < params->rx.n_queues; i++) {
> + rc = rte_eth_rx_queue_setup(port_id, i, params->rx.queue_size,
> numa_node, NULL,
> + mempool);
> + if (rc < 0)
> + return -EINVAL;
> + }
> +
> + /* Port TX */
> + for (i = 0; i < params->tx.n_queues; i++) {
> + rc = rte_eth_tx_queue_setup(port_id, i, params->tx.queue_size,
> numa_node, NULL);
> + if (rc < 0)
> + return -EINVAL;
> + }
> +
> + memcpy(&port_list[port_id].config, params, sizeof(struct ethdev_config));
> + memcpy(port_list[port_id].config.dev_name, name, strlen(name));
> + port_list[port_id].config.port_id = port_id;
> + enabled_port_mask |= RTE_BIT32(port_id);
> + return 0;
> +}
> +
> +static int
> +cmd_ethdev_mtu(char **tokens, uint32_t n_tokens __rte_unused, char *out, size_t
> out_size,
> + void *obj __rte_unused)
> +{
> + int rc = -EINVAL;
> + uint32_t mtu = 0;
> +
> + if (n_tokens != 4) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + return rc;
> + }
> +
> + if (parser_uint32_read(&mtu, tokens[3]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> + return rc;
> + }
> +
> + rc = ethdev_mtu_config(tokens[1], mtu);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> + return rc;
> +}
> +
> +static int
> +cmd_ethdev_prom_mode(char **tokens, uint32_t n_tokens __rte_unused, char *out,
> size_t out_size,
> + void *obj __rte_unused)
> +{
> + bool enable = false;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 4) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + return rc;
> + }
> +
> + if (strcmp(tokens[3], "on") == 0)
> + enable = true;
> +
> + rc = ethdev_prom_mode_config(tokens[1], enable);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> + return rc;
> +}
> +
> +static int
> +cmd_ip4_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + struct ipv4_addr_config config;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 8) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (strcmp(tokens[3], "addr")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[4], "add")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&config.ip, tokens[5])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[6], "netmask")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&config.mask, tokens[7])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> + goto exit;
> + }
> +
> + rc = ethdev_ip4_addr_add(tokens[1], &config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static int
> +cmd_ip6_addr(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + struct ipv6_addr_config config;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 8) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (strcmp(tokens[3], "addr")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "addr");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[4], "add")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> + goto exit;
> + }
> +
> + if (parser_ip6_read(config.ip, tokens[5])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[6], "netmask")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> + goto exit;
> + }
> +
> + if (parser_ip6_read(config.mask, tokens[7])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> + goto exit;
> + }
> +
> + rc = ethdev_ip6_addr_add(tokens[1], &config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static int
> +cmd_ethdev_show(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> + void *obj __rte_unused)
> +{
> + int rc = -EINVAL;
> +
> + if (n_tokens != 3) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + return rc;
> + }
> +
> + rc = ethdev_show(tokens[1], &out, &out_size);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_ARG_INVALID, tokens[0]);
> +
> + return rc;
> +}
> +
> +static int
> +cmd_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + struct ethdev_config config;
> + char *name;
> + int rc;
> +
> + if (n_tokens < 7) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + return -EINVAL;
> + }
> +
> + memset(&config, 0, sizeof(struct ethdev_config));
> + name = tokens[1];
> +
> + if (strcmp(tokens[2], "rxq") != 0) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> + return -EINVAL;
> + }
> +
> + if (parser_uint32_read(&config.rx.n_queues, tokens[3]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> + return -EINVAL;
> + }
> +
> + if (strcmp(tokens[4], "txq") != 0) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "txq");
> + return -EINVAL;
> + }
> +
> + if (parser_uint32_read(&config.tx.n_queues, tokens[5]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "n_queues");
> + return -EINVAL;
> + }
> +
> + mempcpy(config.rx.mempool_name, tokens[6], strlen(tokens[6]));
> +
> + if (n_tokens > 7) {
> + if (strcmp(tokens[7], "mtu") != 0) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "mtu");
> + return -EINVAL;
> + }
> +
> + if (parser_uint32_read(&config.mtu, tokens[8]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "mtu_sz");
> + return -EINVAL;
> + }
> + }
> +
> + config.tx.queue_size = ETHDEV_TX_DESC_DEFAULT;
> + config.rx.queue_size = ETHDEV_RX_DESC_DEFAULT;
> +
> + rc = ethdev_process(name, &config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> + return rc;
> +}
> +
> +static int
> +cli_ethdev_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "----------------------------- ethdev command help -----------------------------");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip4_addr_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_ip6_addr_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_prom_mode_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_mtu_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_show_help);
> +
> + return 0;
> +}
> +
> +static int
> +cli_ethdev(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
> +{
> + if (strcmp(tokens[2], "show") == 0)
> + return cmd_ethdev_show(tokens, n_tokens, out, out_size, obj);
> + else if (strcmp(tokens[2], "mtu") == 0)
> + return cmd_ethdev_mtu(tokens, n_tokens, out, out_size, obj);
> + else if (strcmp(tokens[2], "promiscuous") == 0)
> + return cmd_ethdev_prom_mode(tokens, n_tokens, out, out_size, obj);
> + else if (strcmp(tokens[2], "ip4") == 0)
> + return cmd_ip4_addr(tokens, n_tokens, out, out_size, obj);
> + else if (strcmp(tokens[2], "ip6") == 0)
> + return cmd_ip6_addr(tokens, n_tokens, out, out_size, obj);
> + else
> + return cmd_ethdev(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module ethdev = {
> + .cmd = "ethdev",
> + .process = cli_ethdev,
> + .usage = cli_ethdev_help,
> +};
> +
> +CLI_REGISTER(ethdev);
> diff --git a/app/graph/ethdev.h b/app/graph/ethdev.h
> new file mode 100644
> index 0000000000..9c3de49826
> --- /dev/null
> +++ b/app/graph/ethdev.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_H
> +#define APP_GRAPH_ETHDEV_H
> +
> +#define ETHDEV_IPV6_ADDR_LEN 16
> +
> +struct ipv4_addr_config {
> + uint32_t ip;
> + uint32_t mask;
> +};
> +
> +struct ipv6_addr_config {
> + uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> + uint8_t mask[ETHDEV_IPV6_ADDR_LEN];
> +};
> +
> +extern uint32_t enabled_port_mask;
> +
> +void ethdev_start(void);
> +void ethdev_stop(void);
> +void *ethdev_mempool_list_by_portid(uint16_t portid);
> +int16_t ethdev_portid_by_ip4(uint32_t ip);
> +int16_t ethdev_portid_by_ip6(uint8_t *ip);
> +
> +#endif
> diff --git a/app/graph/ethdev_priv.h b/app/graph/ethdev_priv.h
> new file mode 100644
> index 0000000000..1026c2e5b6
> --- /dev/null
> +++ b/app/graph/ethdev_priv.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_PRIV_H
> +#define APP_GRAPH_ETHDEV_PRIV_H
> +
> +#include "ethdev.h"
> +
> +#define ETHDEV_RXQ_RSS_MAX 16
> +#define ETHDEV_RX_DESC_DEFAULT 1024
> +#define ETHDEV_TX_DESC_DEFAULT 1024
> +
> +struct ethdev_rss_config {
> + uint32_t queue_id[ETHDEV_RXQ_RSS_MAX];
> + uint32_t n_queues;
> +};
> +
> +struct ethdev_config {
> + char dev_name[RTE_ETH_NAME_MAX_LEN];
> + uint16_t port_id;
> +
> + struct {
> + uint32_t n_queues;
> + uint32_t queue_size;
> + char mempool_name[RTE_MEMPOOL_NAMESIZE];
> + struct rte_mempool *mp;
> + struct ethdev_rss_config *rss;
> + } rx;
> +
> + struct {
> + uint32_t n_queues;
> + uint32_t queue_size;
> + } tx;
> +
> + int promiscuous;
> + uint32_t mtu;
> +};
> +
> +struct ethdev {
> + struct ethdev_config config;
> + struct ipv4_addr_config ip4_addr;
> + struct ipv6_addr_config ip6_addr;
> +};
> +
> +#endif
> diff --git a/app/graph/ethdev_rx.c b/app/graph/ethdev_rx.c
> new file mode 100644
> index 0000000000..d706d145c1
> --- /dev/null
> +++ b/app/graph/ethdev_rx.c
> @@ -0,0 +1,139 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_ethdev.h>
> +
> +#include "ethdev_rx_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_ethdev_rx_help[] = "ethdev_rx map port <ethdev_name> queue <q_num> core
> <core_id>";
> +
> +static struct lcore_params lcore_params_array[ETHDEV_RX_LCORE_PARAMS_MAX];
> +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +struct lcore_params *lcore_params = lcore_params_array;
> +struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +uint16_t nb_lcore_params;
> +
> +static void
> +rx_map_configure(uint8_t port_id, uint32_t queue, uint32_t core)
> +{
> + uint8_t n_rx_queue;
> +
> + n_rx_queue = lcore_conf[core].n_rx_queue;
> + lcore_conf[core].rx_queue_list[n_rx_queue].port_id = port_id;
> + lcore_conf[core].rx_queue_list[n_rx_queue].queue_id = queue;
> + lcore_conf[core].n_rx_queue++;
> +}
> +
> +uint8_t
> +ethdev_rx_num_rx_queues_get(uint16_t port)
> +{
> + int queue = -1;
> + uint16_t i;
> +
> + for (i = 0; i < nb_lcore_params; ++i) {
> + if (lcore_params[i].port_id == port) {
> + if (lcore_params[i].queue_id == queue + 1)
> + queue = lcore_params[i].queue_id;
> + else
> + rte_exit(EXIT_FAILURE,
> + "Queue ids of the port %d must be"
> + " in sequence and must start with 0\n",
> + lcore_params[i].port_id);
> + }
> + }
> +
> + return (uint8_t)(++queue);
> +}
> +
> +static int
> +ethdev_rx_map_add(char *name, uint32_t queue, uint32_t core)
> +{
> + uint16_t port_id;
> + int rc;
> +
> + if (nb_lcore_params >= ETHDEV_RX_LCORE_PARAMS_MAX)
> + return -EINVAL;
> +
> + rc = rte_eth_dev_get_port_by_name(name, &port_id);
> + if (rc)
> + return -EINVAL;
> +
> + rx_map_configure(port_id, queue, core);
> +
> + lcore_params_array[nb_lcore_params].port_id = port_id;
> + lcore_params_array[nb_lcore_params].queue_id = queue;
> + lcore_params_array[nb_lcore_params].lcore_id = core;
> + nb_lcore_params++;
> + return 0;
> +}
> +
> +static int
> +cli_ethdev_rx_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "---------------------------- ethdev_rx command help ---------------------------");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ethdev_rx_help);
> + return 0;
> +}
> +
> +static int
> +cli_ethdev_rx(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + char name[RTE_ETH_NAME_MAX_LEN];
> + uint32_t core_id, queue;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 8) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + strcpy(name, tokens[3]);
> +
> + if (strcmp(tokens[4], "queue") != 0) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "rxq");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&queue, tokens[5]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[6], "core") != 0) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "core_id");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&core_id, tokens[7]) != 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "queue");
> + goto exit;
> + }
> +
> + rc = ethdev_rx_map_add(name, queue, core_id);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static struct cli_module ethdev_rx = {
> + .cmd = "ethdev_rx",
> + .process = cli_ethdev_rx,
> + .usage = cli_ethdev_rx_help,
> +};
> +
> +CLI_REGISTER(ethdev_rx);
> diff --git a/app/graph/ethdev_rx.h b/app/graph/ethdev_rx.h
> new file mode 100644
> index 0000000000..d2c18f545f
> --- /dev/null
> +++ b/app/graph/ethdev_rx.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_H
> +#define APP_GRAPH_ETHDEV_RX_H
> +
> +#define ETHDEV_RX_LCORE_PARAMS_MAX 1024
> +#define ETHDEV_RX_QUEUE_PER_LCORE_MAX 16
> +
> +struct lcore_rx_queue {
> + uint16_t port_id;
> + uint8_t queue_id;
> + char node_name[RTE_NODE_NAMESIZE];
> +};
> +
> +struct lcore_conf {
> + uint16_t n_rx_queue;
> + struct lcore_rx_queue rx_queue_list[ETHDEV_RX_QUEUE_PER_LCORE_MAX];
> + struct rte_graph *graph;
> + char name[RTE_GRAPH_NAMESIZE];
> + rte_graph_t graph_id;
> +} __rte_cache_aligned;
> +
> +uint8_t ethdev_rx_num_rx_queues_get(uint16_t port);
> +
> +extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> +extern struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> +extern struct lcore_params *lcore_params;
> +extern uint16_t nb_lcore_params;
> +
> +#endif
> diff --git a/app/graph/ethdev_rx_priv.h b/app/graph/ethdev_rx_priv.h
> new file mode 100644
> index 0000000000..d714f83739
> --- /dev/null
> +++ b/app/graph/ethdev_rx_priv.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ETHDEV_RX_PRIV_H
> +#define APP_GRAPH_ETHDEV_RX_PRIV_H
> +
> +#include <stdint.h>
> +
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#define MAX_RX_QUEUE_PER_PORT 128
> +#define MAX_JUMBO_PKT_LEN 9600
> +#define NB_SOCKETS 8
> +
> +struct lcore_params {
> + uint16_t port_id;
> + uint8_t queue_id;
> + uint8_t lcore_id;
> +} __rte_cache_aligned;
> +
> +#endif
> diff --git a/app/graph/examples/l3fwd.cli b/app/graph/examples/l3fwd.cli
> new file mode 100644
> index 0000000000..9986e1b73e
> --- /dev/null
> +++ b/app/graph/examples/l3fwd.cli
> @@ -0,0 +1,87 @@
> +; SPDX-License-Identifier: BSD-3-Clause
> +; Copyright(c) 2023 Marvell.
> +
> +;
> +; Graph configuration for given usecase
> +;
> +graph l3fwd coremask ff model default
> +
> +;
> +; Mempools to be attached with ethdev
> +;
> +mempool mempool0 size 8192 buffers 4000 cache 256 numa 0
> +
> +;
> +; DPDK devices and configuration.
> +;
> +; Note: Customize the parameters below to match your setup.
> +;
> +ethdev 0002:04:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:05:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:06:00.0 rxq 1 txq 8 mempool0 mtu 1500
> +ethdev 0002:07:00.0 rxq 1 txq 8 mempool0 mtu 1600
> +ethdev 0002:04:00.0 mtu 1700
> +ethdev 0002:05:00.0 promiscuous on
> +
> +;
> +; IPv4 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip4 addr add 10.0.2.1 netmask 255.255.255.0
> +ethdev 0002:05:00.0 ip4 addr add 20.0.2.1 netmask 255.255.255.0
> +ethdev 0002:06:00.0 ip4 addr add 30.0.2.1 netmask 255.255.255.0
> +ethdev 0002:07:00.0 ip4 addr add 40.0.2.1 netmask 255.255.255.0
> +
> +;
> +; IPv6 addresses assigned to DPDK devices
> +;
> +ethdev 0002:04:00.0 ip6 addr add 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:05:00.0 ip6 addr add 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:06:00.0 ip6 addr add 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +ethdev 0002:07:00.0 ip6 addr add 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> netmask FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00
> +
> +;
> +; IPv4 routes which are installed to ipv4_lookup node for LPM processing
> +;
> +ipv4_lookup route add ipv4 10.0.2.0 netmask 255.255.255.0 via 10.0.2.1
> +ipv4_lookup route add ipv4 20.0.2.0 netmask 255.255.255.0 via 20.0.2.1
> +ipv4_lookup route add ipv4 30.0.2.0 netmask 255.255.255.0 via 30.0.2.1
> +ipv4_lookup route add ipv4 40.0.2.0 netmask 255.255.255.0 via 40.0.2.1
> +
> +;
> +; IPv6 routes which are installed to ipv6_lookup node for LPM processing
> +;
> +ipv6_lookup route add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A
> +ipv6_lookup route add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B
> +ipv6_lookup route add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C
> +ipv6_lookup route add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D netmask
> FF:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00 via
> 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D
> +
> +;
> +; Peer MAC and IPv4 address mapping
> +;
> +neigh add ipv4 10.0.2.2 52:20:DA:4F:68:70
> +neigh add ipv4 20.0.2.2 62:20:DA:4F:68:70
> +neigh add ipv4 30.0.2.2 72:20:DA:4F:68:70
> +neigh add ipv4 40.0.2.2 82:20:DA:4F:68:70
> +
> +;
> +; Peer MAC and IPv6 address mapping
> +;
> +neigh add ipv6 52:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4A 52:20:DA:4F:68:70
> +neigh add ipv6 62:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4B 62:20:DA:4F:68:70
> +neigh add ipv6 72:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4C 72:20:DA:4F:68:70
> +neigh add ipv6 82:20:DA:4F:68:70:52:20:DA:4F:68:70:52:20:DA:4D 82:20:DA:4F:68:70
> +
> +;
> +; Port-Queue-Core mapping for ethdev_rx node
> +;
> +ethdev_rx map port 0002:04:00.0 queue 0 core 1
> +ethdev_rx map port 0002:05:00.0 queue 0 core 2
> +ethdev_rx map port 0002:06:00.0 queue 0 core 3
> +ethdev_rx map port 0002:07:00.0 queue 0 core 4
> +
> +;
> +; Graph start command to create graph.
> +;
> +; Note: No more command should come after this.
> +;
> +graph start
> diff --git a/app/graph/graph.c b/app/graph/graph.c
> new file mode 100644
> index 0000000000..8c75574ecd
> --- /dev/null
> +++ b/app/graph/graph.c
> @@ -0,0 +1,383 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_graph_worker.h>
> +#include <rte_log.h>
> +
> +#include "graph_priv.h"
> +#include "module_api.h"
> +
> +#define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
> +
> +static const char
> +cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
> + "model <rtc | mcd | default>";
> +
> +static const char * const supported_usecases[] = {"l3fwd"};
> +struct graph_config graph_config;
> +
> +/* Check the link rc of all ports in up to 9s, and print them finally */
> +static void
> +check_all_ports_link_status(uint32_t port_mask)
> +{
> +#define CHECK_INTERVAL 100 /* 100ms */
> +#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
> + char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
> + uint8_t count, all_ports_up, print_flag = 0;
> + struct rte_eth_link link;
> + uint16_t portid;
> + int rc;
> +
> + printf("\nChecking link rc");
> + fflush(stdout);
> + for (count = 0; count <= MAX_CHECK_TIME; count++) {
> + if (force_quit)
> + return;
> +
> + all_ports_up = 1;
> + RTE_ETH_FOREACH_DEV(portid)
> + {
> + if (force_quit)
> + return;
> +
> + if ((port_mask & (1 << portid)) == 0)
> + continue;
> +
> + memset(&link, 0, sizeof(link));
> + rc = rte_eth_link_get_nowait(portid, &link);
> + if (rc < 0) {
> + all_ports_up = 0;
> + if (print_flag == 1)
> + printf("Port %u link get failed: %s\n",
> + portid, rte_strerror(-rc));
> + continue;
> + }
> +
> + /* Print link rc if flag set */
> + if (print_flag == 1) {
> + rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
> + &link);
> + printf("Port %d %s\n", portid, link_rc_text);
> + continue;
> + }
> +
> + /* Clear all_ports_up flag if any link down */
> + if (link.link_status == RTE_ETH_LINK_DOWN) {
> + all_ports_up = 0;
> + break;
> + }
> + }
> +
> + /* After finally printing all link rc, get out */
> + if (print_flag == 1)
> + break;
> +
> + if (all_ports_up == 0) {
> + printf(".");
> + fflush(stdout);
> + rte_delay_ms(CHECK_INTERVAL);
> + }
> +
> + /* Set the print_flag if all ports up or timeout */
> + if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> + print_flag = 1;
> + printf("Done\n");
> + }
> + }
> +}
> +
> +static bool
> +parser_usecases_read(char *usecases)
> +{
> + bool valid = false;
> + uint32_t i, j = 0;
> + char *token;
> +
> + token = strtok(usecases, ",");
> + while (token != NULL) {
> + for (i = 0; i < RTE_DIM(supported_usecases); i++) {
> + if (strcmp(supported_usecases[i], token) == 0) {
> + graph_config.usecases[j].enabled = true;
> + strcpy(graph_config.usecases[j].name, token);
> + valid = true;
> + j++;
> + break;
> + }
> + }
> + token = strtok(NULL, ",");
> + }
> +
> + return valid;
> +}
> +
> +static uint64_t
> +graph_worker_count_get(void)
> +{
> + uint64_t nb_worker = 0;
> + uint64_t coremask;
> +
> + coremask = graph_config.params.coremask;
> + while (coremask) {
> + if (coremask & 0x1)
> + nb_worker++;
> +
> + coremask = (coremask >> 1);
> + }
> +
> + return nb_worker;
> +}
> +
> +static struct rte_node_ethdev_config *
> +graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
> +{
> + uint32_t n_tx_queue, nb_conf = 0, lcore_id;
> + uint16_t queueid, portid, nb_graphs = 0;
> + uint8_t nb_rx_queue, queue;
> + struct lcore_conf *qconf;
> +
> + n_tx_queue = graph_worker_count_get();
> + if (n_tx_queue > RTE_MAX_ETHPORTS)
> + n_tx_queue = RTE_MAX_ETHPORTS;
> +
> + RTE_ETH_FOREACH_DEV(portid) {
> + /* Skip ports that are not enabled */
> + if ((enabled_port_mask & (1 << portid)) == 0) {
> + printf("\nSkipping disabled port %d\n", portid);
> + continue;
> + }
> +
> + nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
> +
> + /* Setup ethdev node config */
> + ethdev_conf[nb_conf].port_id = portid;
> + ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> + ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
> + ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
> + ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
> +
> + nb_conf++;
> + }
> +
> + for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> + if (rte_lcore_is_enabled(lcore_id) == 0)
> + continue;
> +
> + qconf = &lcore_conf[lcore_id];
> + printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> + fflush(stdout);
> +
> + /* Init RX queues */
> + for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> + portid = qconf->rx_queue_list[queue].port_id;
> + queueid = qconf->rx_queue_list[queue].queue_id;
> +
> + /* Add this queue node to its graph */
> + snprintf(qconf->rx_queue_list[queue].node_name,
> RTE_NODE_NAMESIZE,
> + "ethdev_rx-%u-%u", portid, queueid);
> + }
> + if (qconf->n_rx_queue)
> + nb_graphs++;
> + }
> +
> + printf("\n");
> +
> + ethdev_start();
> + check_all_ports_link_status(enabled_port_mask);
> +
> + *num_conf = nb_conf;
> + *num_graphs = nb_graphs;
> + return ethdev_conf;
> +}
> +
> +static int
> +graph_start(void)
> +{
> + struct rte_node_ethdev_config *conf;
> + uint32_t nb_graphs = 0, nb_conf, i;
> +
> + conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
> + for (i = 0; i < MAX_GRAPH_USECASES; i++) {
> + if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
> + if (graph_config.usecases[i].enabled) {
> + usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
> + break;
> + }
> + }
> + }
> + return 0;
> +}
> +
> +static int
> +graph_config_add(char *usecases, struct graph_config *config)
> +{
> + if (!parser_usecases_read(usecases))
> + return -EINVAL;
> +
> + graph_config.params.bsz = config->params.bsz;
> + graph_config.params.tmo = config->params.tmo;
> + graph_config.params.coremask = config->params.coremask;
> + graph_config.model = config->model;
> +
> + return 0;
> +}
> +
> +int
> +graph_walk_start(void *conf)
> +{
> + struct lcore_conf *qconf;
> + struct rte_graph *graph;
> + uint32_t lcore_id;
> +
> + RTE_SET_USED(conf);
> +
> + lcore_id = rte_lcore_id();
> + qconf = &lcore_conf[lcore_id];
> + graph = qconf->graph;
> +
> + if (!graph) {
> + RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
> + return 0;
> + }
> +
> + RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n",
> lcore_id,
> + qconf->name, graph);
> +
> + while (likely(!force_quit))
> + rte_graph_walk(graph);
> +
> + return 0;
> +}
> +
> +void
> +graph_stats_print(void)
> +{
> + const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> + const char clr[] = {27, '[', '2', 'J', '\0'};
> + struct rte_graph_cluster_stats_param s_param;
> + struct rte_graph_cluster_stats *stats;
> + const char *pattern = "worker_*";
> +
> + /* Prepare stats object */
> + memset(&s_param, 0, sizeof(s_param));
> + s_param.f = stdout;
> + s_param.socket_id = SOCKET_ID_ANY;
> + s_param.graph_patterns = &pattern;
> + s_param.nb_graph_patterns = 1;
> +
> + stats = rte_graph_cluster_stats_create(&s_param);
> + if (stats == NULL)
> + rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> +
> + while (!force_quit) {
> + /* Clear screen and move to top left */
> + printf("%s%s", clr, topLeft);
> + rte_graph_cluster_stats_get(stats, 0);
> + rte_delay_ms(1E3);
> + }
> +
> + rte_graph_cluster_stats_destroy(stats);
> +}
> +
> +static void
> +graph_config_process(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> + void *obj __rte_unused)
> +{
> + uint32_t bsz = 32, tmo = 0, coremask = 0xf;
> + struct graph_config config;
> + int idx = 2, rc;
> + uint8_t model;
> +
> + if (n_tokens < 4) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + return;
> + }
> +
> +next_arg:
> + if (strcmp(tokens[idx], "model")) {
> + if (strcmp(tokens[idx], "bsz") == 0) {
> + if (parser_uint32_read(&bsz, tokens[idx + 1])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "bsz");
> + return;
> + }
> +
> + } else if (strcmp(tokens[idx], "tmo") == 0) {
> + if (parser_uint32_read(&tmo, tokens[idx + 1])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> + return;
> + }
> + } else if (strcmp(tokens[idx], "coremask") == 0) {
> + coremask = strtol(tokens[idx + 1], NULL, 16);
> + if (coremask == 0) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "tmo");
> + return;
> + }
> + } else {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "usecases
> params");
> + return;
> + }
> +
> + idx += 2;
> + goto next_arg;
> + } else {
> + if (strcmp(tokens[idx + 1], "default") == 0) {
> + model = GRAPH_MODEL_RTC;
> + } else if (strcmp(tokens[idx + 1], "rtc") == 0) {
> + model = GRAPH_MODEL_RTC;
> + } else if (strcmp(tokens[idx + 1], "mcd") == 0) {
> + model = GRAPH_MODEL_MCD;
> + } else {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "model
> arguments");
> + return;
> + }
> + }
> +
> + config.params.bsz = bsz;
> + config.params.tmo = tmo;
> + config.params.coremask = coremask;
> + config.model = model;
> + rc = graph_config_add(tokens[1], &config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +}
> +
> +static int
> +cli_graph_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "----------------------------- graph command help -----------------------------");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_graph_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", "graph start");
> + return 0;
> +}
> +
> +static int
> +cli_graph(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + if (strcmp(tokens[1], "start") == 0)
> + graph_start();
> + else
> + graph_config_process(tokens, n_tokens, out, out_size, obj);
> +
> + return 0;
> +}
> +
> +static struct cli_module graph = {
> + .cmd = "graph",
> + .process = cli_graph,
> + .usage = cli_graph_help,
> +};
> +
> +CLI_REGISTER(graph);
> diff --git a/app/graph/graph.h b/app/graph/graph.h
> new file mode 100644
> index 0000000000..126e967d75
> --- /dev/null
> +++ b/app/graph/graph.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_H
> +#define APP_GRAPH_H
> +
> +int graph_walk_start(void *conf);
> +void graph_stats_print(void);
> +
> +#endif
> diff --git a/app/graph/graph_priv.h b/app/graph/graph_priv.h
> new file mode 100644
> index 0000000000..655a028fb2
> --- /dev/null
> +++ b/app/graph/graph_priv.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_PRIV_H
> +#define APP_GRAPH_PRIV_H
> +
> +#define MAX_GRAPH_USECASES 32
> +
> +enum graph_model {
> + GRAPH_MODEL_RTC = 0x01,
> + GRAPH_MODEL_MCD = 0x02,
> +};
> +
> +struct usecases {
> + char name[32];
> + bool enabled;
> +};
> +
> +struct usecase_params {
> + uint64_t coremask;
> + uint32_t bsz;
> + uint32_t tmo;
> +};
> +
> +struct graph_config {
> + struct usecases usecases[MAX_GRAPH_USECASES];
> + struct usecase_params params;
> + enum graph_model model;
> +};
> +
> +#endif
> diff --git a/app/graph/ip4_route.c b/app/graph/ip4_route.c
> new file mode 100644
> index 0000000000..5aba5b38f2
> --- /dev/null
> +++ b/app/graph/ip4_route.c
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv4_lookup_help[] = "ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>";
> +
> +struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_netmask_to_depth(uint32_t netmask)
> +{
> + uint8_t zerobits = 0;
> +
> + while ((netmask & 0x1) == 0) {
> + netmask = netmask >> 1;
> + zerobits++;
> + }
> +
> + return (32 - zerobits);
> +}
> +
> +static int
> +route_ip4_add(struct ipv4_route_config *route)
> +{
> + int i;
> +
> + for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> + if (!route4[i].is_used)
> + break;
> + }
> +
> + if (i == MAX_ROUTE_ENTRIES)
> + return -ENOMEM;
[Nithin] Change neigh and route database to dynamic linked list instead of using static array.
> +
> + route4[i].ip = route->ip;
> + route4[i].netmask = route->netmask;
> + route4[i].via = route->via;
> + route4[i].is_used = true;
> + return 0;
> +}
> +
> +int
> +route_ip4_add_to_lookup(void)
> +{
> + struct ipv4_route_config *route = NULL;
> + int rc = -EINVAL;
> + uint8_t depth;
> + int portid, i;
> +
> + for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> + if (route4[i].is_used)
> + route = &route4[i];
> +
> + portid = ethdev_portid_by_ip4(route->via);
> + if (portid < 0) {
> + printf("Invalid portid found to install the route\n");
> + return rc;
> + }
> +
> + depth = convert_netmask_to_depth(route->netmask);
> +
> + rc = rte_node_ip4_route_add(route->ip, depth, portid,
> + RTE_NODE_IP4_LOOKUP_NEXT_REWRITE);
> + if (rc < 0)
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused,
> char *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "--------------------------- ipv4_lookup command help --------------------------
> ");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ipv4_lookup_help);
> + return 0;
> +}
> +
> +static int
> +cli_ipv4_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> + void *obj __rte_unused)
> +{
> + struct ipv4_route_config config;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 9) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&config.ip, tokens[4])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ipv4");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[5], "netmask")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&config.netmask, tokens[6])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[7], "via")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&config.via, tokens[8])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "via ip");
> + goto exit;
> + }
> +
> + rc = route_ip4_add(&config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static struct cli_module ipv4_lookup = {
> + .cmd = "ipv4_lookup",
> + .process = cli_ipv4_lookup,
> + .usage = cli_ipv4_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv4_lookup);
> diff --git a/app/graph/ip6_route.c b/app/graph/ip6_route.c
> new file mode 100644
> index 0000000000..2c5397f9d3
> --- /dev/null
> +++ b/app/graph/ip6_route.c
> @@ -0,0 +1,154 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip6_api.h>
> +
> +#include "module_api.h"
> +
> +static const char
> +cmd_ipv6_lookup_help[] = "ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>";
> +
> +struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +static uint8_t
> +convert_ip6_netmask_to_depth(uint8_t *netmask)
> +{
> + uint8_t setbits = 0;
> + uint8_t mask;
> + int i;
> +
> + for (i = 0; i < ETHDEV_IPV6_ADDR_LEN; i++) {
> + mask = netmask[i];
> + while (mask & 0x80) {
> + mask = mask << 1;
> + setbits++;
> + }
> + }
> +
> + return setbits;
> +}
> +
> +static int
> +route_ip6_add(struct ipv6_route_config *route)
> +{
> + int i, j;
> +
> + for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
> + if (!route6[i].is_used)
> + break;
> + }
> +
> + if (i == RTE_MAX_ETHPORTS)
> + return -ENOMEM;
> +
> + for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++) {
> + route6[i].ip[j] = route->ip[j];
> + route6[i].mask[j] = route->mask[j];
> + route6[i].gateway[j] = route->gateway[j];
> + }
> + route6[i].is_used = true;
> +
> + return 0;
> +}
> +
> +int
> +route_ip6_add_to_lookup(void)
> +{
> + struct ipv6_route_config *route = NULL;
> + int rc = -EINVAL;
> + uint8_t depth;
> + int portid, i;
> +
> + for (i = 0; i < MAX_ROUTE_ENTRIES; i++) {
> + if (route6[i].is_used)
> + route = &route6[i];
> +
> + portid = ethdev_portid_by_ip6(route->gateway);
> + if (portid < 0) {
> + printf("Invalid portid found to install the route\n");
> + return rc;
> + }
> +
> + depth = convert_ip6_netmask_to_depth(route->mask);
> +
> + rc = rte_node_ip6_route_add(route->ip, depth, portid,
> + RTE_NODE_IP6_LOOKUP_NEXT_REWRITE);
> + if (rc < 0)
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused,
> char *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "--------------------------- ipv6_lookup command help --------------------------
> ");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_ipv6_lookup_help);
> + return 0;
> +}
> +
> +static int
> +cli_ipv6_lookup(char **tokens, uint32_t n_tokens, char *out, size_t out_size,
> + void *obj __rte_unused)
> +{
> + struct ipv6_route_config config;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 9) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (parser_ip6_read(config.ip, tokens[4])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ipv6");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[5], "netmask")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "netmask");
> + goto exit;
> + }
> +
> + if (parser_ip6_read(config.mask, tokens[6])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "netmask");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[7], "via")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "via");
> + goto exit;
> + }
> +
> + if (parser_ip6_read(config.gateway, tokens[8])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "gateway ip");
> + goto exit;
> + }
> +
> + rc = route_ip6_add(&config);
> + if (rc)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static struct cli_module ipv6_lookup = {
> + .cmd = "ipv6_lookup",
> + .process = cli_ipv6_lookup,
> + .usage = cli_ipv6_lookup_help,
> +};
> +
> +CLI_REGISTER(ipv6_lookup);
> diff --git a/app/graph/l3fwd.c b/app/graph/l3fwd.c
> new file mode 100644
> index 0000000000..85b8b2618e
> --- /dev/null
> +++ b/app/graph/l3fwd.c
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +#include <rte_lcore.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "module_api.h"
> +
> +static char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
> +static uint64_t packet_to_capture;
> +static int pcap_trace_enable;
> +
> +static int
> +l3fwd_pattern_configure(void)
> +{
> + /* Graph initialization. 8< */
> + static const char * const default_patterns[] = {
> + "ip4*",
> + "ethdev_tx-*",
> + "pkt_drop",
> + };
> +
> + struct rte_graph_param graph_conf;
> + const char **node_patterns;
> + struct lcore_conf *qconf;
> + uint16_t nb_patterns;
> + uint8_t lcore_id;
> + int rc;
> +
> + nb_patterns = RTE_DIM(default_patterns);
> + node_patterns = malloc((ETHDEV_RX_QUEUE_PER_LCORE_MAX + nb_patterns) *
> + sizeof(*node_patterns));
> + if (!node_patterns)
> + return -ENOMEM;
> + memcpy(node_patterns, default_patterns,
> + nb_patterns * sizeof(*node_patterns));
> +
> + memset(&graph_conf, 0, sizeof(graph_conf));
> + graph_conf.node_patterns = node_patterns;
> +
> + /* Pcap config */
> + graph_conf.pcap_enable = pcap_trace_enable;
> + graph_conf.num_pkt_to_capture = packet_to_capture;
> + graph_conf.pcap_filename = pcap_filename;
> +
> + for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> + rte_graph_t graph_id;
> + rte_edge_t i;
> +
> + if (rte_lcore_is_enabled(lcore_id) == 0)
> + continue;
> +
> + qconf = &lcore_conf[lcore_id];
> +
> + /* Skip graph creation if no source exists */
> + if (!qconf->n_rx_queue)
> + continue;
> +
> + /* Add rx node patterns of this lcore */
> + for (i = 0; i < qconf->n_rx_queue; i++) {
> + graph_conf.node_patterns[nb_patterns + i] =
> + qconf->rx_queue_list[i].node_name;
> + }
> +
> + graph_conf.nb_node_patterns = nb_patterns + i;
> + graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> +
> + snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> + lcore_id);
> +
> + graph_id = rte_graph_create(qconf->name, &graph_conf);
> + if (graph_id == RTE_GRAPH_ID_INVALID)
> + rte_exit(EXIT_FAILURE,
> + "rte_graph_create(): graph_id invalid"
> + " for lcore %u\n", lcore_id);
> +
> + qconf->graph_id = graph_id;
> + qconf->graph = rte_graph_lookup(qconf->name);
> + /* >8 End of graph initialization. */
> + if (!qconf->graph)
> + rte_exit(EXIT_FAILURE,
> + "rte_graph_lookup(): graph %s not found\n",
> + qconf->name);
> + }
> +
> + rc = route_ip4_add_to_lookup();
> + if (rc < 0)
> + rte_exit(EXIT_FAILURE, "Unable to add v4 route to lookup table\n");
> +
> + rc = route_ip6_add_to_lookup();
> + if (rc < 0)
> + rte_exit(EXIT_FAILURE, "Unable to add v6 route to lookup table\n");
> +
> + rc = neigh_ip4_add_to_rewrite();
> + if (rc < 0)
> + rte_exit(EXIT_FAILURE, "Unable to add v4 to rewrite node\n");
> +
> + rc = neigh_ip6_add_to_rewrite();
> + if (rc < 0)
> + rte_exit(EXIT_FAILURE, "Unable to add v6 to rewrite node\n");
> +
> + /* Launch per-lcore init on every worker lcore */
> + rte_eal_mp_remote_launch(graph_walk_start, NULL, SKIP_MAIN);
> +
> + /* Accumulate and print stats on main until exit */
> + if (rte_graph_has_stats_feature() && app_graph_stats_enabled())
> + graph_stats_print();
> +
> + /* Wait for worker cores to exit */
> + rc = 0;
> + RTE_LCORE_FOREACH_WORKER(lcore_id) {
> + rc = rte_eal_wait_lcore(lcore_id);
> + /* Destroy graph */
> + if (rc < 0 ||
> + rte_graph_destroy(rte_graph_from_name(lcore_conf[lcore_id].name)))
> {
> + rc = -1;
> + break;
> + }
> + }
> + free(node_patterns);
> +
> + ethdev_stop();
> + return rc;
> +}
> +
> +int
> +usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs,
> uint16_t nb_graphs)
> +{
> + int rc;
> +
> + rc = rte_node_eth_config(conf, nb_confs, nb_graphs);
> + if (rc)
> + rte_exit(EXIT_FAILURE, "rte_node_eth_config: err=%d\n", rc);
> +
> + rc = l3fwd_pattern_configure();
> + if (rc)
> + rte_exit(EXIT_FAILURE, "l3fwd_pattern_failure: err=%d\n", rc);
> +
> + return rc;
> +}
> diff --git a/app/graph/l3fwd.h b/app/graph/l3fwd.h
> new file mode 100644
> index 0000000000..e1d23165e6
> --- /dev/null
> +++ b/app/graph/l3fwd.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_L3FWD_H
> +#define APP_GRAPH_L3FWD_H
> +
> +int usecase_l3fwd_configure(struct rte_node_ethdev_config *conf, uint16_t nb_conf,
> + uint16_t nb_graphs);
> +
> +#endif
> diff --git a/app/graph/main.c b/app/graph/main.c
> new file mode 100644
> index 0000000000..e9934025bf
> --- /dev/null
> +++ b/app/graph/main.c
> @@ -0,0 +1,201 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <rte_eal.h>
> +#include <rte_launch.h>
> +
> +#include "module_api.h"
> +
> +volatile bool force_quit;
> +
> +static const char usage[] = "%s EAL_ARGS -- -s SCRIPT [-h HOST] [-p PORT] [--enable-graph-
> stats] "
> + "[--help]\n";
> +
> +static struct app_params {
> + struct conn_params conn;
> + char *script_name;
> + bool enable_graph_stats;
> +} app = {
> + .conn = {
> + .welcome = "\nWelcome!\n\n",
> + .prompt = "graph> ",
> + .addr = "0.0.0.0",
> + .port = 8086,
> + .buf_size = 1024 * 1024,
> + .msg_in_len_max = 1024,
> + .msg_out_len_max = 1024 * 1024,
> + .msg_handle = cli_process,
> + .msg_handle_arg = NULL, /* set later. */
> + },
> + .script_name = NULL,
> + .enable_graph_stats = false,
> +};
> +
> +static void
> +signal_handler(int signum)
> +{
> + if (signum == SIGINT || signum == SIGTERM) {
> + printf("\n\nSignal %d received, preparing to exit...\n", signum);
> + force_quit = true;
> + }
> +}
> +
> +static int
> +app_args_parse(int argc, char **argv)
> +{
> + struct option lgopts[] = {
> + {"help", 0, 0, 'H'},
> + {"enable-graph-stats", 0, 0, 'g'},
> + };
> + int h_present, p_present, s_present, n_args, i;
> + char *app_name = argv[0];
> + int opt, option_index;
> +
> + /* Skip EAL input args */
> + n_args = argc;
> + for (i = 0; i < n_args; i++)
> + if (strcmp(argv[i], "--") == 0) {
> + argc -= i;
> + argv += i;
> + break;
> + }
> +
> + if (i == n_args)
> + return 0;
> +
> + /* Parse args */
> + h_present = 0;
> + p_present = 0;
> + s_present = 0;
> +
> + while ((opt = getopt_long(argc, argv, "h:p:s:", lgopts, &option_index)) != EOF) {
> + switch (opt) {
> + case 'h':
> + if (h_present) {
> + printf("Error: Multiple -h arguments\n");
> + return -1;
> + }
> + h_present = 1;
> +
> + if (!strlen(optarg)) {
> + printf("Error: Argument for -h not provided\n");
> + return -1;
> + }
> +
> + app.conn.addr = strdup(optarg);
> + if (app.conn.addr == NULL) {
> + printf("Error: Not enough memory\n");
> + return -1;
> + }
> + break;
> +
> + case 'p':
> + if (p_present) {
> + printf("Error: Multiple -p arguments\n");
> + return -1;
> + }
> + p_present = 1;
> +
> + if (!strlen(optarg)) {
> + printf("Error: Argument for -p not provided\n");
> + return -1;
> + }
> +
> + app.conn.port = (uint16_t) atoi(optarg);
> + break;
> +
> + case 's':
> + if (s_present) {
> + printf("Error: Multiple -s arguments\n");
> + return -1;
> + }
> + s_present = 1;
> +
> + if (!strlen(optarg)) {
> + printf("Error: Argument for -s not provided\n");
> + return -1;
> + }
> +
> + app.script_name = strdup(optarg);
> + if (app.script_name == NULL) {
> + printf("Error: Not enough memory\n");
> + return -1;
> + }
> + break;
> +
> + case 'g':
> + app.enable_graph_stats = true;
> + break;
> +
> + case 'H':
> + default:
> + printf(usage, app_name);
> + return -1;
> + }
> + }
> + optind = 1; /* reset getopt lib */
> +
> + return 0;
> +}
> +
> +bool
> +app_graph_stats_enabled(void)
> +{
> + return app.enable_graph_stats;
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> + struct conn *conn;
> + int rc;
> +
> + /* Parse application arguments */
> + rc = app_args_parse(argc, argv);
> + if (rc < 0)
> + return rc;
> +
> + /* EAL */
> + rc = rte_eal_init(argc, argv);
> + if (rc < 0) {
> + printf("Error: EAL initialization failed (%d)\n", rc);
> + return rc;
> + };
> +
> + force_quit = false;
> + signal(SIGINT, signal_handler);
> + signal(SIGTERM, signal_handler);
> +
> + /* Script */
> + if (app.script_name) {
> + cli_script_process(app.script_name, app.conn.msg_in_len_max,
> + app.conn.msg_out_len_max, NULL);
> + }
> +
> + /* Connectivity */
> + app.conn.msg_handle_arg = NULL;
> + conn = conn_init(&app.conn);
> + if (!conn) {
> + printf("Error: Connectivity initialization failed (%d)\n", rc);
> + return rc;
> + };
> +
> + /* Dispatch loop */
> + while (1) {
> + conn_req_poll(conn);
> +
> + conn_msg_poll(conn);
> + }
> +
> + /* clean up the EAL */
> + rte_eal_cleanup();
> +}
> diff --git a/app/graph/mempool.c b/app/graph/mempool.c
> new file mode 100644
> index 0000000000..1cee66abed
> --- /dev/null
> +++ b/app/graph/mempool.c
> @@ -0,0 +1,134 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_mbuf.h>
> +
> +#include "mempool_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_mempool_help[] = "mempool <mempool_name> size <mbuf_size> buffers
> <number_of_buffers> "
> + "cache <cache_size> numa <numa_id>";
> +
> +struct mempools mpconfig;
> +
> +int
> +mempool_process(struct mempool_config *config)
> +{
> + struct rte_mempool *mp;
> + uint8_t nb_pools;
> +
> + nb_pools = mpconfig.nb_pools;
> + strcpy(mpconfig.config[nb_pools].name, config->name);
> + mpconfig.config[nb_pools].pool_size = config->pool_size;
> + mpconfig.config[nb_pools].buffer_size = config->buffer_size;
> + mpconfig.config[nb_pools].cache_size = config->cache_size;
> + mpconfig.config[nb_pools].numa_node = config->numa_node;
> +
> + mp = rte_pktmbuf_pool_create(config->name, config->pool_size, config-
> >cache_size,
> + 64, config->buffer_size, config->numa_node);
> + if (!mp)
> + return -EINVAL;
> +
> + mpconfig.mp[nb_pools] = mp;
> + nb_pools++;
> + mpconfig.nb_pools = nb_pools;
> +
> + return 0;
> +}
> +
> +static int
> +cli_mempool_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char
> *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "---------------------------- mempool command help ----------------------------");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_mempool_help);
> + return 0;
> +}
> +
> +static int
> +cli_mempool(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + uint32_t pkt_buffer_size, pool_size, cache_size, numa_node;
> + struct mempool_config config;
> + int rc = -EINVAL;
> +
> + if (n_tokens != 10) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (strcmp(tokens[2], "size")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "size");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&pkt_buffer_size, tokens[3])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "mbuf_size");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[4], "buffers")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "buffers");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&pool_size, tokens[5])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "number_of_buffers");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[6], "cache")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "cache");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&cache_size, tokens[7])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "cache_size");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[8], "numa")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "numa");
> + goto exit;
> + }
> +
> + if (parser_uint32_read(&numa_node, tokens[9])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "numa_id");
> + goto exit;
> + }
> +
> + strcpy(config.name, tokens[1]);
> + config.name[strlen(tokens[1])] = '\0';
> + config.pool_size = pool_size;
> + config.buffer_size = pkt_buffer_size;
> + config.cache_size = cache_size;
> + config.numa_node = numa_node;
> +
> + rc = mempool_process(&config);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static struct cli_module mempool = {
> + .cmd = "mempool",
> + .process = cli_mempool,
> + .usage = cli_mempool_help,
> +};
> +
> +CLI_REGISTER(mempool);
> diff --git a/app/graph/mempool.h b/app/graph/mempool.h
> new file mode 100644
> index 0000000000..5fc788199d
> --- /dev/null
> +++ b/app/graph/mempool.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_H
> +#define APP_GRAPH_MEMPOOL_H
> +
> +struct mempool_config {
> + char name[RTE_MEMPOOL_NAMESIZE];
> + int pool_size;
> + int cache_size;
> + int buffer_size;
> + int numa_node;
> +};
> +
> +int mempool_process(struct mempool_config *config);
> +
> +#endif
> diff --git a/app/graph/mempool_priv.h b/app/graph/mempool_priv.h
> new file mode 100644
> index 0000000000..5a55722b32
> --- /dev/null
> +++ b/app/graph/mempool_priv.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MEMPOOL_PRIV_H
> +#define APP_GRAPH_MEMPOOL_PRIV_H
> +
> +#include "mempool.h"
> +
> +struct mempools {
> + struct mempool_config config[RTE_MAX_ETHPORTS];
> + struct rte_mempool *mp[RTE_MAX_ETHPORTS];
> + uint8_t nb_pools;
> +};
> +
> +#endif
> diff --git a/app/graph/meson.build b/app/graph/meson.build
> new file mode 100644
> index 0000000000..a3011e504b
> --- /dev/null
> +++ b/app/graph/meson.build
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2023 Marvell.
> +
> +# override default name to drop the hyphen
> +name = 'graph'
> +build = cc.has_header('sys/epoll.h')
> +if not build
> + subdir_done()
> +endif
> +
> +deps += ['bus_pci', 'graph', 'eal', 'lpm', 'ethdev', 'node']
> +sources = files(
> + 'cli.c',
> + 'conn.c',
> + 'ethdev_rx.c',
> + 'ethdev.c',
> + 'graph.c',
> + 'ip4_route.c',
> + 'ip6_route.c',
> + 'main.c',
> + 'mempool.c',
> + 'neigh.c',
> + 'l3fwd.c',
> + 'utils.c',
> +)
> diff --git a/app/graph/module_api.h b/app/graph/module_api.h
> new file mode 100644
> index 0000000000..09b10bc672
> --- /dev/null
> +++ b/app/graph/module_api.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_MODULE_API_H
> +#define APP_GRAPH_MODULE_API_H
> +
> +#include <stdint.h>
> +
> +#include <rte_common.h>
> +#include <rte_ethdev.h>
> +#include <rte_graph.h>
> +#include <rte_node_eth_api.h>
> +
> +#include "conn.h"
> +#include "cli.h"
> +#include "ethdev.h"
> +#include "ethdev_rx.h"
> +#include "graph.h"
> +#include "l3fwd.h"
> +#include "mempool.h"
> +#include "neigh.h"
> +#include "route.h"
> +#include "utils.h"
> +
> +/*
> + * Externs
> + */
> +extern volatile bool force_quit;
> +
> +bool app_graph_stats_enabled(void);
> +
> +#endif
> diff --git a/app/graph/neigh.c b/app/graph/neigh.c
> new file mode 100644
> index 0000000000..07766758c9
> --- /dev/null
> +++ b/app/graph/neigh.c
> @@ -0,0 +1,269 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_node_ip4_api.h>
> +#include <rte_node_ip6_api.h>
> +
> +#include "neigh_priv.h"
> +#include "module_api.h"
> +
> +static const char
> +cmd_neigh_v4_help[] = "neigh add ipv4 <ip> <mac>";
> +
> +static const char
> +cmd_neigh_v6_help[] = "neigh add ipv6 <ip> <mac>";
> +
> +struct ipv4_neigh_config neigh4[MAX_NEIGH_ENTRIES];
> +struct ipv6_neigh_config neigh6[MAX_NEIGH_ENTRIES];
> +
> +static int
> +neigh_ip4_add(uint32_t ip, uint64_t mac)
> +{
> + int i;
> +
> + for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> + if (!neigh4[i].is_used)
> + break;
> + }
> +
> + if (i == MAX_NEIGH_ENTRIES)
> + return -ENOMEM;
> +
> + neigh4[i].ip = ip;
> + neigh4[i].mac = mac;
> + neigh4[i].is_used = true;
> + return 0;
> +}
> +
> +static int
> +neigh_ip6_add(uint8_t *ip, uint64_t mac)
> +{
> + int i, j;
> +
> + for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> + if (!neigh6[i].is_used)
> + break;
> + }
> +
> + if (i == MAX_NEIGH_ENTRIES)
> + return -ENOMEM;
> +
> + for (j = 0; j < ETHDEV_IPV6_ADDR_LEN; j++)
> + neigh6[i].ip[j] = ip[j];
> +
> + neigh6[i].mac = mac;
> + neigh6[i].is_used = true;
> + return 0;
> +}
> +
> +int
> +neigh_ip4_add_to_rewrite(void)
> +{
> + uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> + uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> + struct rte_ether_addr smac = {0};
> + struct ipv4_neigh_config *neigh;
> + int16_t portid = 0;
> + int rc, i;
> +
> + for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> + if (!neigh4[i].is_used)
> + continue;
> +
> + neigh = &neigh4[i];
> + portid = ethdev_portid_by_ip4(neigh->ip);
> + if (portid < 0) {
> + printf("Invalid portid found to add neigh\n");
> + return -EINVAL;
> + }
> +
> + memset(data, 0, len);
> +
> + /* Copy dst mac */
> + rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> + /* Copy src mac */
> + rc = rte_eth_macaddr_get(portid, &smac);
> + if (rc < 0) {
> + printf("Cannot get MAC address: err=%d, port=%d\n", rc, portid);
> + return rc;
> + }
> +
> + rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> + rc = rte_node_ip4_rewrite_add(portid, data, len, portid);
> + if (rc < 0) {
> + printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
> + return rc;
> + }
> + }
> +
> + return 0;
> +}
> +
> +int
> +neigh_ip6_add_to_rewrite(void)
> +{
> + uint8_t data[2 * RTE_ETHER_ADDR_LEN];
> + uint8_t len = 2 * RTE_ETHER_ADDR_LEN;
> + struct rte_ether_addr smac = {0};
> + struct ipv6_neigh_config *neigh;
> + int16_t portid = 0;
> + int rc, i;
> +
> + for (i = 0; i < MAX_NEIGH_ENTRIES; i++) {
> + if (!neigh6[i].is_used)
> + continue;
> +
> + neigh = &neigh6[i];
> + portid = ethdev_portid_by_ip6(neigh->ip);
> + if (portid < 0) {
> + printf("Invalid portid found to add neigh\n");
> + return -EINVAL;
> + }
> +
> + memset(data, 0, len);
> +
> + /* Copy dst mac */
> + rte_memcpy((void *)&data[0], (void *)&neigh->mac,
> RTE_ETHER_ADDR_LEN);
> +
> + /* Copy src mac */
> + rc = rte_eth_macaddr_get(portid, &smac);
> + if (rc < 0) {
> + printf("Cannot get MAC address: err=%d, port=%d\n",
> + rc, portid);
> + return rc;
> + }
> +
> + rte_memcpy(&data[RTE_ETHER_ADDR_LEN], smac.addr_bytes,
> RTE_ETHER_ADDR_LEN);
> +
> + rc = rte_node_ip6_rewrite_add(portid, data, len, portid);
> + if (rc < 0) {
> + printf("Error in writing rewrite data: err=%d, port=%d\n", rc, portid);
> + return rc;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int
> +cmd_neigh_v4(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + int rc = -EINVAL;
> + uint64_t mac;
> + uint32_t ip;
> +
> + if (n_tokens != 5) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (strcmp(tokens[1], "add")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[2], "ipv4")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv4");
> + goto exit;
> + }
> +
> + if (parser_ip4_read(&ip, tokens[3])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> + goto exit;
> + }
> +
> + if (parser_mac_read(&mac, tokens[4])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> + goto exit;
> + }
> +
> + rc = neigh_ip4_add(ip, mac);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static int
> +cmd_neigh_v6(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj
> __rte_unused)
> +{
> + uint8_t ip[ETHDEV_IPV6_ADDR_LEN];
> + int rc = -EINVAL;
> + uint64_t mac;
> +
> + if (n_tokens != 5) {
> + snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
> + goto exit;
> + }
> +
> + if (strcmp(tokens[1], "add")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "add");
> + goto exit;
> + }
> +
> + if (strcmp(tokens[2], "ipv6")) {
> + snprintf(out, out_size, MSG_ARG_NOT_FOUND, "ipv6");
> + goto exit;
> + }
> +
> + if (parser_ip6_read(ip, tokens[3])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "ip");
> + goto exit;
> + }
> +
> + if (parser_mac_read(&mac, tokens[4])) {
> + snprintf(out, out_size, MSG_ARG_INVALID, "mac");
> + goto exit;
> + }
> +
> + rc = neigh_ip6_add(ip, mac);
> + if (rc < 0)
> + snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
> +
> +exit:
> + return rc;
> +}
> +
> +static int
> +cli_neigh_help(char **tokens __rte_unused, uint32_t n_tokens __rte_unused, char *out,
> + size_t out_size, void *obj __rte_unused)
> +{
> + size_t len;
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "\n%s\n",
> + "----------------------------- neigh command help -----------------------------");
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_neigh_v4_help);
> +
> + len = strlen(out);
> + snprintf(out + len, out_size, "%s\n", cmd_neigh_v6_help);
> + return 0;
> +}
> +
> +static int
> +cli_neigh(char **tokens, uint32_t n_tokens, char *out, size_t out_size, void *obj)
> +{
> + if (strcmp(tokens[2], "ipv4") == 0)
> + return cmd_neigh_v4(tokens, n_tokens, out, out_size, obj);
> + else
> + return cmd_neigh_v6(tokens, n_tokens, out, out_size, obj);
> +}
> +
> +static struct cli_module neigh = {
> + .cmd = "neigh",
> + .process = cli_neigh,
> + .usage = cli_neigh_help,
> +};
> +
> +CLI_REGISTER(neigh);
[Nithin] Move logic for tokenizing into cmdline library itself. Module should only register the commands to libcmdline.
> diff --git a/app/graph/neigh.h b/app/graph/neigh.h
> new file mode 100644
> index 0000000000..3964c37bb0
> --- /dev/null
> +++ b/app/graph/neigh.h
> @@ -0,0 +1,11 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_H
> +#define APP_GRAPH_NEIGH_H
> +
> +int neigh_ip4_add_to_rewrite(void);
> +int neigh_ip6_add_to_rewrite(void);
> +
> +#endif
> diff --git a/app/graph/neigh_priv.h b/app/graph/neigh_priv.h
> new file mode 100644
> index 0000000000..745dc7d671
> --- /dev/null
> +++ b/app/graph/neigh_priv.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_NEIGH_PRIV_H
> +#define APP_GRAPH_NEIGH_PRIV_H
> +
> +#define MAX_NEIGH_ENTRIES 32
> +
> +struct ipv4_neigh_config {
> + uint32_t ip;
> + uint64_t mac;
> + bool is_used;
> +};
> +
> +struct ipv6_neigh_config {
> + uint8_t ip[16];
> + uint64_t mac;
> + bool is_used;
> +};
> +
> +#endif
> diff --git a/app/graph/route.h b/app/graph/route.h
> new file mode 100644
> index 0000000000..6b4acf3344
> --- /dev/null
> +++ b/app/graph/route.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_ROUTE_H
> +#define APP_GRAPH_ROUTE_H
> +
> +#define MAX_ROUTE_ENTRIES 32
> +
> +struct ipv4_route_config {
> + uint32_t ip;
> + uint32_t netmask;
> + uint32_t via;
> + bool is_used;
> +};
> +
> +struct ipv6_route_config {
> + uint8_t ip[16];
> + uint8_t mask[16];
> + uint8_t gateway[16];
> + bool is_used;
> +};
> +
> +extern struct ipv4_route_config route4[MAX_ROUTE_ENTRIES];
> +extern struct ipv6_route_config route6[MAX_ROUTE_ENTRIES];
> +
> +int route_ip4_add_to_lookup(void);
> +int route_ip6_add_to_lookup(void);
> +
> +#endif
> diff --git a/app/graph/utils.c b/app/graph/utils.c
> new file mode 100644
> index 0000000000..48a83e738c
> --- /dev/null
> +++ b/app/graph/utils.c
> @@ -0,0 +1,155 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#include <ctype.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +
> +#include "module_api.h"
> +
> +#define white_spaces_skip(pos) \
> +({ \
> + __typeof__(pos) _p = (pos); \
> + for ( ; isspace(*_p); _p++) \
> + ; \
> + _p; \
> +})
> +
> +static void
> +hex_string_to_uint64(uint64_t *dst, const char *hexs)
> +{
> + char buf[2] = {0};
> + uint8_t shift = 4;
> + int iter = 0;
> + char c;
> +
> + while ((c = *hexs++)) {
> + buf[0] = c;
> + *dst |= (strtol(buf, NULL, 16) << shift);
> + shift -= 4;
> + iter++;
> + if (iter == 2) {
> + iter = 0;
> + shift = 4;
> + dst++;
> + }
> + }
> +}
> +
> +int
> +parser_uint64_read(uint64_t *value, const char *p)
> +{
> + char *next;
> + uint64_t val;
> +
> + p = white_spaces_skip(p);
> + if (!isdigit(*p))
> + return -EINVAL;
> +
> + val = strtoul(p, &next, 0);
> + if (p == next)
> + return -EINVAL;
> +
> + p = next;
> + switch (*p) {
> + case 'T':
> + val *= 1024ULL;
> + /* fall through */
> + case 'G':
> + val *= 1024ULL;
> + /* fall through */
> + case 'M':
> + val *= 1024ULL;
> + /* fall through */
> + case 'k':
> + case 'K':
> + val *= 1024ULL;
> + p++;
> + break;
> + }
> +
> + p = white_spaces_skip(p);
> + if (*p != '\0')
> + return -EINVAL;
> +
> + *value = val;
> + return 0;
> +}
> +
> +int
> +parser_uint32_read(uint32_t *value, const char *p)
> +{
> + uint64_t val = 0;
> + int rc = parser_uint64_read(&val, p);
> +
> + if (rc < 0)
> + return rc;
> +
> + if (val > UINT32_MAX)
> + return -ERANGE;
> +
> + *value = val;
> + return 0;
> +}
> +
> +int
> +parser_ip4_read(uint32_t *value, char *p)
> +{
> + uint8_t shift = 24;
> + uint32_t ip = 0;
> + char *token;
> +
> + token = strtok(p, ".");
> + while (token != NULL) {
> + ip |= (atoi(token) << shift);
> + token = strtok(NULL, ".");
> + shift -= 8;
> + }
> +
> + *value = ip;
> +
> + return 0;
> +}
> +
> +int
> +parser_ip6_read(uint8_t *value, char *p)
> +{
> + uint64_t val = 0;
> + char *token;
> +
> + token = strtok(p, ":");
> + while (token != NULL) {
> + hex_string_to_uint64(&val, token);
> + *value = val;
> + token = strtok(NULL, ":");
> + value++;
> + val = 0;
> + }
> +
> + return 0;
> +}
> +
> +int
> +parser_mac_read(uint64_t *value, char *p)
> +{
> + uint64_t mac = 0, val = 0;
> + uint8_t shift = 40;
> + char *token;
> +
> + token = strtok(p, ":");
> + while (token != NULL) {
> + hex_string_to_uint64(&val, token);
> + mac |= val << shift;
> + token = strtok(NULL, ":");
> + shift -= 8;
> + val = 0;
> + }
> +
> + *value = mac;
> +
> + return 0;
> +}
> diff --git a/app/graph/utils.h b/app/graph/utils.h
> new file mode 100644
> index 0000000000..0ebb5de55a
> --- /dev/null
> +++ b/app/graph/utils.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Marvell.
> + */
> +
> +#ifndef APP_GRAPH_UTILS_H
> +#define APP_GRAPH_UTILS_H
> +
> +int parser_uint64_read(uint64_t *value, const char *p);
> +int parser_uint32_read(uint32_t *value, const char *p);
> +int parser_ip4_read(uint32_t *value, char *p);
> +int parser_ip6_read(uint8_t *value, char *p);
> +int parser_mac_read(uint64_t *value, char *p);
> +
> +#endif
> diff --git a/app/meson.build b/app/meson.build
> index e4bf5c531c..728c936383 100644
> --- a/app/meson.build
> +++ b/app/meson.build
> @@ -17,6 +17,7 @@ endif
> apps = [
> 'dumpcap',
> 'pdump',
> + 'graph',
> 'proc-info',
> 'test-acl',
> 'test-bbdev',
> diff --git a/doc/guides/tools/graph.rst b/doc/guides/tools/graph.rst
> new file mode 100644
> index 0000000000..c90dc9ad7f
> --- /dev/null
> +++ b/doc/guides/tools/graph.rst
> @@ -0,0 +1,171 @@
> +.. SPDX-License-Identifier: BSD-3-Clause
> + Copyright(c) 2023 Marvell.
> +
> +dpdk-graph Application
> +======================
> +
> +The ``dpdk-graph`` tool is a Data Plane Development Kit (DPDK)
> +application that allows exercising various graph use cases.
> +This application has a generic framework to add new graph based use cases to
> +verify functionality. Each use case is defined as a ``<usecase>.cli`` file.
> +Based on the input file, application creates a graph to cater the use case.
> +
> +Supported Use cases
> +-------------------
> + * l3fwd
> +
> +Running the Application
> +-----------------------
> +
> +The application has a number of command line options which can be provided in
> +following syntax
> +
> +.. code-block:: console
> +
> + dpdk-graph [EAL Options] -- [application options]
> +
> +EAL Options
> +~~~~~~~~~~~
> +
> +Following are the EAL command-line options that can be used in conjunction
> +with the ``dpdk-graph`` application.
> +See the DPDK Getting Started Guides for more information on these options.
> +
> +* ``-c <COREMASK>`` or ``-l <CORELIST>``
> +
> + Set the hexadecimal bit mask of the cores to run on. The CORELIST is a
> + list of cores to be used.
> +
> +Application Options
> +~~~~~~~~~~~~~~~~~~~
> +
> +Following are the application command-line options:
> +
> +* ``-h``
> +
> + Set the host IPv4 address over which telnet session can be opened.
> + It is an optional parameter. Default host address is 0.0.0.0.
> +
> +* ``-p``
> +
> + Set the L4 port number over which telnet session can be opened.
> + It is an optional parameter. Default port is 8086.
> +
> +* ``-s``
> +
> + Script name with absolute path which specifies the use case. It is
> + a mandatory parameter which will be used to create desired graph
> + for a given use case.
> +
> +* ``--enable-graph-stats``
> +
> + Enable graph statistics printing on console. By default graph statistics are disabled.
> +
> +* ``--help``
> +
> + Dumps application usage
> +
> +Supported CLI commands
> +----------------------
> +
> +This section provides details on commands which can be used in ``<usecase>.cli``
> +file to express the requested use case configuration.
> +
> +.. list-table:: Exposed CLIs
> + :header-rows: 1
> + :widths: auto
> +
> + * - Command
> + - Description
> + - Dynamic
> + - Optional
> + * - graph <usecases> [bsz <size>] [tmo <ns>] [coremask <bitmask>] model <rtc | mcd |
> default>
> + - Command to express the desired use case
> + - No
> + - No
> + * - graph start
> + - Command to start the graph.
> + This command triggers that no more commands are left to be parsed and graph
> + initialization can be started now. It must be the last command in ``<usecase>.cli``
> + - No
> + - No
> + * - mempool <mempool_name> size <mbuf_size> buffers <number_of_buffers> cache
> <cache_size> numa <numa_id>
> + - Command to create mempool which will be further associated to RxQ to dequeue the
> packets
> + - No
> + - No
> + * - ethdev <ethdev_name> rxq <n_queues> txq <n_queues> <mempool_name> [mtu
> <mtu_sz>]
> + - Command to create DPDK port with given number of Rx and Tx queues. Also attached
> + RxQ with given mempool. Each port can have single mempool only i.e. all RxQs will
> + share the same mempool.
> + - No
> + - No
> + * - ethdev <ethdev_name> mtu <mtu_sz>
> + - Command to configure MTU of DPDK port
> + - Yes
> + - Yes
> + * - ethdev <ethdev_name> promiscuous <on/off>
> + - Command to enable/disable promiscuous mode on DPDK port
> + - Yes
> + - Yes
> + * - ethdev <ethdev_name> show
> + - Command to dump current ethdev configuration
> + - Yes
> + - Yes
> + * - ethdev <ethdev_name> ip4 addr add <ip> netmask <mask>
> + - Command to configure IPv4 address on given PCI device. It is needed if user
> + wishes to use ``ipv4_lookup`` node
> + - No
> + - Yes
> + * - ethdev <ethdev_name> ip6 addr add <ip> netmask <mask>
> + - Command to configure IPv6 address on given PCI device. It is needed if user
> + wishes to use ``ipv6_lookup`` node
> + - No
> + - Yes
> + * - ipv4_lookup route add ipv4 <ip> netmask <mask> via <ip>
> + - Command to add a route into ``ipv4_lookup`` LPM table. It is needed if user
> + wishes to route the packets based on LPM lookup table.
> + - No
> + - Yes
> + * - ipv6_lookup route add ipv6 <ip> netmask <mask> via <ip>
> + - Command to add a route into ``ipv6_lookup`` LPM table. It is needed if user
> + wishes to route the packets based on LPM6 lookup table.
> + - No
> + - Yes
> + * - neigh add ipv4 <ip> <mac>
> + - Command to add a neighbour information into ``ipv4_rewrite`` node.
> + - No
> + - Yes
> + * - neigh add ipv6 <ip> <mac>
> + - Command to add a neighbour information into ``ipv6_rewrite`` node.
> + - No
> + - Yes
> + * - ethdev_rx map port <ethdev_name> queue <q_num> core <core_id>
> + - Command to add port-queue-core mapping to ``ethdev_rx`` node. ``ethdev_rx``
> + node instance will be pinned on given core and will poll on requested
> + port/queue pair.
> + - No
> + - No
> +
[Nithin] Add CLI to get ethdev stats, graph stats via telnet
> +Runtime configuration
> +---------------------
> +
> +Application allows some configuration to be modified at runtime using a telnet session.
> +Application initiates a telnet server with host address 0.0.0.0 and port number 8086 if
> +``-h`` and ``-p`` is not given otherwise user provided IPv4 address and port number will
> +be used.
> +
> +After successful launch of application, client can connect to using host/port address and
> +console will be accessed with prompt ``graph>``.
[Nithin] Update Telnet session connection example with log
> +
> +Created graph for use case
> +--------------------------
> +
> +On the successful execution of ``<usecase>.cli`` file, corresponding graph will be created.
> +This section mentions the created graph for each use case.
> +
> +l3fwd
> +~~~~~
> +
> +.. _figure_l3fwd_graph:
> +
> +.. figure:: img/graph-usecase-l3fwd.*
> diff --git a/doc/guides/tools/img/graph-usecase-l3fwd.svg b/doc/guides/tools/img/graph-
> usecase-l3fwd.svg
> new file mode 100644
> index 0000000000..3b991c4cf0
> --- /dev/null
> +++ b/doc/guides/tools/img/graph-usecase-l3fwd.svg
> @@ -0,0 +1,210 @@
> +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
> + "https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_Graphics_SVG_1.1_DTD_svg11.dtd&d=DwIDAg&c=nKjWec2b6R0mOyPa
> z7xtfQ&r=FZ_tPCbgFOh18zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfAp
> ezlB2TtUrWk19QRB6M5nl_A9wsLObEPqlXYSqUau7ZfBV&s=QNoHRxZ4WaV84k-
> 6IlzN8d9bMQsg8Go9iFXzE0lgT-o&e= ">
> +<!-- Generated by graphviz version 2.43.0 (0)
> + -->
> +<!-- SPDX-License-Identifier: BSD-3-Clause -->
> +<!-- Copyright(C) 2023 Marvell. -->
> +<!--
> +
> +Generated with following command
> +dot -Tsvg dot.dot -o doc/guides/tools/img/graph-usecase-l3fwd.svg
> +
> +cat dot.dot
> +digraph dpdk_app_graph_l3fwd_nodes_flow {
> + ingress_port [shape=rect]
> + ethdev_rx
> + pkt_cls
> + ip4_lookup
> + ip6_lookup
> + ip4_rewrite
> + ip6_rewrite
> + ethdev_tx
> + pkt_drop
> + egress_port [shape=rect]
> +
> + ingress_port -> ethdev_rx [label="ingress packet"]
> +
> + ethdev_rx -> pkt_cls
> +
> + pkt_cls -> ip4_lookup [color="green"]
> + pkt_cls -> ip6_lookup [color="blue"]
> + pkt_cls -> pkt_drop [color="red" style="dashed"]
> +
> + ip4_lookup -> ip4_rewrite [color="green"]
> + ip4_lookup -> pkt_drop [color="red" style="dashed"]
> +
> + ip6_lookup -> ip6_rewrite [color="blue"]
> + ip6_lookup -> pkt_drop [color="red" style="dashed"]
> +
> + ip4_rewrite -> ethdev_tx [color="green"]
> + ip4_rewrite -> pkt_drop [color="red" style="dashed"]
> +
> + ip6_rewrite -> ethdev_tx [color="blue"]
> + ip6_rewrite -> pkt_drop [color="red" style="dashed"]
> +
> + ethdev_tx -> egress_port [label="egress packet"]
> + ethdev_tx -> pkt_drop [color="red" style="dashed"]
> +}
> +
> + -->
> +<!-- Title: dpdk_app_graph_l3fwd_nodes_flow Pages: 1 -->
> +<svg width="550pt" height="510pt"
> + viewBox="0.00 0.00 549.50 510.00"
> xmlns="https://urldefense.proofpoint.com/v2/url?u=http-
> 3A__www.w3.org_2000_svg&d=DwIDAg&c=nKjWec2b6R0mOyPaz7xtfQ&r=FZ_tPCbgFOh1
> 8zwRPO9H0yDx8VW38vuapifdDfc8SFQ&m=Ee0JsG0yj736hYMlfApezlB2TtUrWk19QRB6M5nl
> _A9wsLObEPqlXYSqUau7ZfBV&s=KLiZ62_Z8HSL_a9Mq0OR-PCG_h1JavRUfbKsPOc4IAo&e=
> " xmlns:xlink="http://www.w3.org/1999/xlink">
> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 506)">
> +<title>dpdk_app_graph_l3fwd_nodes_flow</title>
> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-506 545.5,-506 545.5,4 -4,4"/>
> +<!-- ingress_port -->
> +<g id="node1" class="node">
> +<title>ingress_port</title>
> +<polygon fill="none" stroke="black" points="489.5,-502 383.5,-502 383.5,-466 489.5,-466
> 489.5,-502"/>
> +<text text-anchor="middle" x="436.5" y="-480.3" font-family="Times,serif" font-
> size="14.00">ingress_port</text>
> +</g>
> +<!-- ethdev_rx -->
> +<g id="node2" class="node">
> +<title>ethdev_rx</title>
> +<ellipse fill="none" stroke="black" cx="436.5" cy="-397" rx="56.59" ry="18"/>
> +<text text-anchor="middle" x="436.5" y="-393.3" font-family="Times,serif" font-
> size="14.00">ethdev_rx</text>
> +</g>
> +<!-- ingress_port->ethdev_rx -->
> +<g id="edge1" class="edge">
> +<title>ingress_port->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->pkt_cls -->
> +<g id="edge2" class="edge">
> +<title>ethdev_rx->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->ip4_lookup -->
> +<g id="edge3" class="edge">
> +<title>pkt_cls->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->ip6_lookup -->
> +<g id="edge4" class="edge">
> +<title>pkt_cls->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->pkt_drop -->
> +<g id="edge5" class="edge">
> +<title>pkt_cls->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->ip4_rewrite -->
> +<g id="edge6" class="edge">
> +<title>ip4_lookup->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->pkt_drop -->
> +<g id="edge7" class="edge">
> +<title>ip4_lookup->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->ip6_rewrite -->
> +<g id="edge8" class="edge">
> +<title>ip6_lookup->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->pkt_drop -->
> +<g id="edge9" class="edge">
> +<title>ip6_lookup->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->ethdev_tx -->
> +<g id="edge10" class="edge">
> +<title>ip4_rewrite->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->pkt_drop -->
> +<g id="edge11" class="edge">
> +<title>ip4_rewrite->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->ethdev_tx -->
> +<g id="edge12" class="edge">
> +<title>ip6_rewrite->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->pkt_drop -->
> +<g id="edge13" class="edge">
> +<title>ip6_rewrite->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->pkt_drop -->
> +<g id="edge15" class="edge">
> +<title>ethdev_tx->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->egress_port -->
> +<g id="edge14" class="edge">
> +<title>ethdev_tx->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
@@ -1806,6 +1806,13 @@ F: dts/
F: devtools/dts-check-format.sh
F: doc/guides/tools/dts.rst
+Graph application
+M: Sunil Kumar Kori <skori@marvell.com>
+M: Rakesh Kudurumalla <rkudurumalla@marvell.com>
+F: app/graph/
+F: doc/guides/tools/graph.rst
+F: doc/guides/tools/img/graph-usecase-l3fwd.svg
+
Other Example Applications
--------------------------
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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);
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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);
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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',
+)
new file mode 100644
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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
@@ -17,6 +17,7 @@ endif
apps = [
'dumpcap',
'pdump',
+ 'graph',
'proc-info',
'test-acl',
'test-bbdev',
new file mode 100644
@@ -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.*
new file mode 100644
@@ -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->ethdev_rx -->
+<g id="edge1" class="edge">
+<title>ingress_port->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->pkt_cls -->
+<g id="edge2" class="edge">
+<title>ethdev_rx->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->ip4_lookup -->
+<g id="edge3" class="edge">
+<title>pkt_cls->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->ip6_lookup -->
+<g id="edge4" class="edge">
+<title>pkt_cls->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->pkt_drop -->
+<g id="edge5" class="edge">
+<title>pkt_cls->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->ip4_rewrite -->
+<g id="edge6" class="edge">
+<title>ip4_lookup->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->pkt_drop -->
+<g id="edge7" class="edge">
+<title>ip4_lookup->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->ip6_rewrite -->
+<g id="edge8" class="edge">
+<title>ip6_lookup->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->pkt_drop -->
+<g id="edge9" class="edge">
+<title>ip6_lookup->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->ethdev_tx -->
+<g id="edge10" class="edge">
+<title>ip4_rewrite->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->pkt_drop -->
+<g id="edge11" class="edge">
+<title>ip4_rewrite->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->ethdev_tx -->
+<g id="edge12" class="edge">
+<title>ip6_rewrite->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->pkt_drop -->
+<g id="edge13" class="edge">
+<title>ip6_rewrite->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->pkt_drop -->
+<g id="edge15" class="edge">
+<title>ethdev_tx->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->egress_port -->
+<g id="edge14" class="edge">
+<title>ethdev_tx->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>
@@ -23,4 +23,5 @@ DPDK Tools User Guides
testeventdev
testregex
testmldev
+ graph
dts