* [PATCH] app/test-pmd: add generic PROG action parser support
@ 2026-05-21 5:56 Megha Ajmera
2026-05-21 10:13 ` [PATCH v2] " Megha Ajmera
0 siblings, 1 reply; 13+ messages in thread
From: Megha Ajmera @ 2026-05-21 5:56 UTC (permalink / raw)
To: bruce.richardson, cristian.dumitrescu, praveen.shetty, dev
Add parser support for a generic PROG flow action in testpmd.
The update adds CLI tokens and parsing logic for program name and
argument tuples (name, size, value), enabling programmable action
configuration through the flow command interface.
Example flow rule:
flow create 0 ingress pattern eth / end actions prog name my_prog
argument name arg0 size 4 value 10 / end
Signed-off-by: Megha Ajmera <megha.ajmera@intel.com>
Signed-off-by: Praveen Shetty <praveen.shetty@intel.com>
---
app/test-pmd/cmdline_flow.c | 584 +++++++++++++++++++++++++++++++++++-
1 file changed, 581 insertions(+), 3 deletions(-)
diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index ebc036b14b..b429ff7267 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -714,6 +714,13 @@ enum index {
ACTION_SET_META,
ACTION_SET_META_DATA,
ACTION_SET_META_MASK,
+ /* ACTION PROG */
+ ACTION_PROG,
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_SET_IPV6_DSCP,
@@ -981,6 +988,24 @@ struct action_sample_data {
struct rte_flow_action_sample conf;
uint32_t idx;
};
+
+#define ACTION_PROG_MAX_ARGS 32
+#define ACTION_PROG_NAME_LEN_MAX 64
+#define ACTION_PROG_ARG_NAME_LEN_MAX 16
+
+struct action_prog_argument_data {
+ char name[ACTION_PROG_ARG_NAME_LEN_MAX];
+ uint32_t length;
+ uint32_t size;
+ uint64_t value;
+};
+
+struct action_prog_data {
+ char name[ACTION_PROG_NAME_LEN_MAX];
+ uint32_t args_num;
+ uint32_t length;
+ struct action_prog_argument_data args[ACTION_PROG_MAX_ARGS];
+};
/** Storage for struct rte_flow_action_sample. */
struct raw_sample_conf {
struct rte_flow_action data[ACTION_SAMPLE_ACTIONS_NUM];
@@ -2310,6 +2335,7 @@ static const enum index next_action[] = {
ACTION_RAW_DECAP,
ACTION_SET_TAG,
ACTION_SET_META,
+ ACTION_PROG,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV6_DSCP,
ACTION_AGE,
@@ -2565,6 +2591,23 @@ static const enum index action_set_meta[] = {
ZERO,
};
+/** ACTION PROG */
+static const enum index next_action_prog[] = {
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
+static const enum index next_prog_arg[] = {
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
static const enum index action_set_ipv4_dscp[] = {
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_NEXT,
@@ -2803,6 +2846,27 @@ static int parse_vc_action_set_meta(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
unsigned int size);
+static int parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size);
+static int parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static void free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num);
static int parse_vc_action_sample(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
@@ -7816,6 +7880,48 @@ static const struct token token_list[] = {
(struct rte_flow_action_set_meta, mask)),
.call = parse_vc_conf,
},
+ [ACTION_PROG] = {
+ .name = "prog",
+ .help = "Program action: action prog name <name> [argument ...]",
+ .priv = PRIV_ACTION(PROG,
+ sizeof(struct action_prog_data)),
+ .next = NEXT(next_action_prog),
+ .call = parse_vc_action_prog,
+ },
+ [ACTION_PROG_NAME] = {
+ .name = "name",
+ .help = "Action name",
+ .next = NEXT(next_action_prog, NEXT_ENTRY(COMMON_STRING)),
+ .args = ARGS(ARGS_ENTRY_ARB(0, 0),
+ ARGS_ENTRY(struct action_prog_data, length),
+ ARGS_ENTRY_ARB(0,
+ ACTION_PROG_NAME_LEN_MAX)),
+ .call = parse_vc_conf,
+ },
+ [ACTION_PROG_ARGUMENT] = {
+ .name = "argument",
+ .help = "Keyword: argument",
+ .next = NEXT(next_prog_arg),
+ .call = parse_vc_action_prog_argument,
+ },
+ [ACTION_PROG_ARGUMENT_NAME] = {
+ .name = "name",
+ .help = "Argument Name",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_STRING)),
+ .call = parse_vc_action_prog_argument_name,
+ },
+ [ACTION_PROG_ARGUMENT_SIZE] = {
+ .name = "size",
+ .help = "Argument size (bytes)",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_size,
+ },
+ [ACTION_PROG_ARGUMENT_VALUE] = {
+ .name = "value",
+ .help = "Argument value",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_value,
+ },
[ACTION_SET_IPV4_DSCP] = {
.name = "set_ipv4_dscp",
.help = "set DSCP value",
@@ -10415,6 +10521,449 @@ parse_vc_action_set_meta(struct context *ctx, const struct token *token,
return len;
}
+/** Parse PROG action */
+static int
+parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ int ret;
+
+ ret = parse_vc(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return ret;
+
+ if (!out->args.vc.actions_n)
+ return -1;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ prog_data->args_num = 0;
+
+ return ret;
+}
+
+/** Called when args keyword is encountered */
+static int
+parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(size);
+
+ /* Token name must match. */
+ if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+ return -1;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return len;
+
+ if (!out->args.vc.actions_n)
+ return len;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num >= ACTION_PROG_MAX_ARGS)
+ return -1;
+ prog_data->args_num++;
+
+ return len;
+}
+
+static int __prog_argument_name_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg_addr[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_len[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_data[ACTION_PROG_MAX_ARGS];
+ /* Calculate the base size based on the actual structure size */
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg_addr[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_addr[arg_ind].size = 0;
+
+ if (push_args(ctx, &arg_addr[arg_ind]))
+ return -1;
+ arg_len[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, length) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_len[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->length);
+
+ if (push_args(ctx, &arg_len[arg_ind]))
+ return -1;
+ arg_data[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_data[arg_ind].size =
+ sizeof(((struct action_prog_argument_data *)0)->name);
+
+ if (push_args(ctx, &arg_data[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int __prog_argument_size_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, size) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->size);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int __prog_argument_value_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, value) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->value);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int
+parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ RTE_SET_USED(size);
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+/**
+ * Convert action_prog_data to rte_flow_action_prog.
+ * Converts PROG actions from action_prog_data format to rte_flow_action_prog format.
+ *
+ * @param actions The flow actions array
+ * @param action_count Optional count of actions. If 0, will count until END action
+ * @return 0 on success, negative value on failure
+ */
+static int
+convert_action_prog_to_rte_flow(struct rte_flow_action *actions,
+ uint32_t prog_action_count,
+ uint32_t *converted_idx,
+ uint32_t converted_idx_cap,
+ uint32_t *converted_idx_num)
+{
+ uint32_t i = 0;
+ uint32_t j;
+ uint32_t k;
+ const struct action_prog_data *prog_data;
+ struct rte_flow_action_prog *prog;
+ struct rte_flow_action_prog_argument *args;
+ uint8_t *value;
+ bool arg_error;
+ int ret = 0;
+
+ if (converted_idx_num)
+ *converted_idx_num = 0;
+
+ /* If action_count is 0, count the actions until END action */
+ if (prog_action_count == 0) {
+ while (actions[i].type != RTE_FLOW_ACTION_TYPE_END)
+ i++;
+ prog_action_count = i + 1; /* Include END action */
+ i = 0; /* Reset counter for the processing loop */
+ }
+
+ /* Process all actions */
+ for (i = 0; i < prog_action_count; i++) {
+ if (actions[i].type == RTE_FLOW_ACTION_TYPE_PROG) {
+ prog_data = (const struct action_prog_data *)actions[i].conf;
+ if (!prog_data) {
+ fprintf(stderr, "Prog action found but no data provided\n");
+ ret = -EINVAL;
+ continue;
+ }
+
+ prog = calloc(1, sizeof(struct rte_flow_action_prog));
+ if (!prog) {
+ fprintf(stderr, "Failed to allocate memory for prog action\n");
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->name = strdup(prog_data->name);
+ if (!prog->name) {
+ fprintf(stderr, "Failed to allocate memory for prog name\n");
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->args_num = prog_data->args_num;
+
+ if (prog->args_num > 0) {
+ args = calloc(prog->args_num,
+ sizeof(struct rte_flow_action_prog_argument));
+ if (!args) {
+ fprintf(stderr,
+ "Failed to allocate memory for prog arguments\n");
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ arg_error = false;
+ for (j = 0; j < prog->args_num; j++) {
+ args[j].name = strdup(prog_data->args[j].name);
+ if (!args[j].name) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument name\n");
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ args[j].size = prog_data->args[j].size;
+ if (args[j].size == 0)
+ continue;
+ if (args[j].size > sizeof(prog_data->args[j].value)) {
+ free((void *)(uintptr_t)args[j].name);
+ arg_error = true;
+ ret = -EINVAL;
+ break;
+ }
+
+ value = malloc(args[j].size);
+ if (!value) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument value\n");
+ free((void *)(uintptr_t)args[j].name);
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ memcpy(value,
+ &prog_data->args[j].value,
+ args[j].size);
+ args[j].value = value;
+ }
+
+ if (arg_error) {
+ /* Free all allocated resources */
+ for (k = 0; k < j; k++) {
+ free((void *)(uintptr_t)args[k].name);
+ free((void *)(uintptr_t)args[k].value);
+ }
+ free(args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ continue;
+ }
+
+ prog->args = args;
+ }
+
+ actions[i].conf = prog;
+ if (converted_idx && converted_idx_num &&
+ *converted_idx_num < converted_idx_cap)
+ converted_idx[(*converted_idx_num)++] = i;
+ }
+ }
+
+ return ret;
+}
+
+static void
+free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num)
+{
+ uint32_t i;
+
+ for (i = 0; i < converted_idx_num; i++) {
+ uint32_t idx = converted_idx[i];
+ struct rte_flow_action_prog *prog;
+ uint32_t j;
+
+ if (actions[idx].type != RTE_FLOW_ACTION_TYPE_PROG)
+ continue;
+
+ prog = actions[idx].conf;
+ if (!prog)
+ continue;
+
+ for (j = 0; j < prog->args_num; j++) {
+ free((void *)(uintptr_t)prog->args[j].name);
+ free((void *)(uintptr_t)prog->args[j].value);
+ }
+ free(prog->args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ }
+}
+
static int
parse_vc_action_sample(struct context *ctx, const struct token *token,
const char *str, unsigned int len, void *buf,
@@ -13380,6 +13929,8 @@ indirect_action_list_conf_get(uint32_t conf_id)
static void
cmd_flow_parsed(const struct buffer *in)
{
+ int ret;
+
switch (in->command) {
case INFO:
port_flow_get_info(in->port);
@@ -13561,9 +14112,36 @@ cmd_flow_parsed(const struct buffer *in)
&in->args.vc.tunnel_ops);
break;
case CREATE:
- port_flow_create(in->port, &in->args.vc.attr,
- in->args.vc.pattern, in->args.vc.actions,
- &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ {
+ uint32_t *converted_idx;
+ uint32_t converted_idx_num = 0;
+
+ converted_idx = calloc(in->args.vc.actions_n,
+ sizeof(*converted_idx));
+ if (!converted_idx) {
+ fprintf(stderr,
+ "Warning: Failed to allocate conversion index list\n");
+ break;
+ }
+
+ /* Convert from action_prog_data to rte_flow_action_prog. */
+ ret = convert_action_prog_to_rte_flow(in->args.vc.actions,
+ 0,
+ converted_idx,
+ in->args.vc.actions_n,
+ &converted_idx_num);
+ if (ret < 0)
+ fprintf(stderr,
+ "Warning: Failed to convert program action data: %s\n",
+ strerror(-ret));
+ port_flow_create(in->port, &in->args.vc.attr,
+ in->args.vc.pattern, in->args.vc.actions,
+ &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ free_action_prog_converted(in->args.vc.actions,
+ converted_idx,
+ converted_idx_num);
+ free(converted_idx);
+ }
break;
case DESTROY:
port_flow_destroy(in->port, in->args.destroy.rule_n,
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-21 5:56 [PATCH] app/test-pmd: add generic PROG action parser support Megha Ajmera
@ 2026-05-21 10:13 ` Megha Ajmera
2026-05-26 19:22 ` Stephen Hemminger
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Megha Ajmera @ 2026-05-21 10:13 UTC (permalink / raw)
To: bruce.richardson, cristian.dumitrescu, praveen.shetty, dev
Add parser support for a generic PROG flow action in testpmd.
The update adds CLI tokens and parsing logic for program name and
argument tuples (name, size, value), enabling programmable action
configuration through the flow command interface.
Example flow rule:
flow create 0 ingress pattern eth / end actions prog name my_prog
argument name arg0 size 4 value 10 / end
Signed-off-by: Megha Ajmera <megha.ajmera@intel.com>
Signed-off-by: Praveen Shetty <praveen.shetty@intel.com>
---
v2:
* Fixed compilation warning.
app/test-pmd/cmdline_flow.c | 584 +++++++++++++++++++++++++++++++++++-
1 file changed, 581 insertions(+), 3 deletions(-)
diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index ebc036b14b..daf1de7146 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -714,6 +714,13 @@ enum index {
ACTION_SET_META,
ACTION_SET_META_DATA,
ACTION_SET_META_MASK,
+ /* ACTION PROG */
+ ACTION_PROG,
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_SET_IPV6_DSCP,
@@ -981,6 +988,24 @@ struct action_sample_data {
struct rte_flow_action_sample conf;
uint32_t idx;
};
+
+#define ACTION_PROG_MAX_ARGS 32
+#define ACTION_PROG_NAME_LEN_MAX 64
+#define ACTION_PROG_ARG_NAME_LEN_MAX 16
+
+struct action_prog_argument_data {
+ char name[ACTION_PROG_ARG_NAME_LEN_MAX];
+ uint32_t length;
+ uint32_t size;
+ uint64_t value;
+};
+
+struct action_prog_data {
+ char name[ACTION_PROG_NAME_LEN_MAX];
+ uint32_t args_num;
+ uint32_t length;
+ struct action_prog_argument_data args[ACTION_PROG_MAX_ARGS];
+};
/** Storage for struct rte_flow_action_sample. */
struct raw_sample_conf {
struct rte_flow_action data[ACTION_SAMPLE_ACTIONS_NUM];
@@ -2310,6 +2335,7 @@ static const enum index next_action[] = {
ACTION_RAW_DECAP,
ACTION_SET_TAG,
ACTION_SET_META,
+ ACTION_PROG,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV6_DSCP,
ACTION_AGE,
@@ -2565,6 +2591,23 @@ static const enum index action_set_meta[] = {
ZERO,
};
+/** ACTION PROG */
+static const enum index next_action_prog[] = {
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
+static const enum index next_prog_arg[] = {
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
static const enum index action_set_ipv4_dscp[] = {
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_NEXT,
@@ -2803,6 +2846,27 @@ static int parse_vc_action_set_meta(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
unsigned int size);
+static int parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size);
+static int parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static void free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num);
static int parse_vc_action_sample(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
@@ -7816,6 +7880,48 @@ static const struct token token_list[] = {
(struct rte_flow_action_set_meta, mask)),
.call = parse_vc_conf,
},
+ [ACTION_PROG] = {
+ .name = "prog",
+ .help = "Program action: action prog name <name> [argument ...]",
+ .priv = PRIV_ACTION(PROG,
+ sizeof(struct action_prog_data)),
+ .next = NEXT(next_action_prog),
+ .call = parse_vc_action_prog,
+ },
+ [ACTION_PROG_NAME] = {
+ .name = "name",
+ .help = "Action name",
+ .next = NEXT(next_action_prog, NEXT_ENTRY(COMMON_STRING)),
+ .args = ARGS(ARGS_ENTRY_ARB(0, 0),
+ ARGS_ENTRY(struct action_prog_data, length),
+ ARGS_ENTRY_ARB(0,
+ ACTION_PROG_NAME_LEN_MAX)),
+ .call = parse_vc_conf,
+ },
+ [ACTION_PROG_ARGUMENT] = {
+ .name = "argument",
+ .help = "Keyword: argument",
+ .next = NEXT(next_prog_arg),
+ .call = parse_vc_action_prog_argument,
+ },
+ [ACTION_PROG_ARGUMENT_NAME] = {
+ .name = "name",
+ .help = "Argument Name",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_STRING)),
+ .call = parse_vc_action_prog_argument_name,
+ },
+ [ACTION_PROG_ARGUMENT_SIZE] = {
+ .name = "size",
+ .help = "Argument size (bytes)",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_size,
+ },
+ [ACTION_PROG_ARGUMENT_VALUE] = {
+ .name = "value",
+ .help = "Argument value",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_value,
+ },
[ACTION_SET_IPV4_DSCP] = {
.name = "set_ipv4_dscp",
.help = "set DSCP value",
@@ -10415,6 +10521,449 @@ parse_vc_action_set_meta(struct context *ctx, const struct token *token,
return len;
}
+/** Parse PROG action */
+static int
+parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ int ret;
+
+ ret = parse_vc(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return ret;
+
+ if (!out->args.vc.actions_n)
+ return -1;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ prog_data->args_num = 0;
+
+ return ret;
+}
+
+/** Called when args keyword is encountered */
+static int
+parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(size);
+
+ /* Token name must match. */
+ if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+ return -1;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return len;
+
+ if (!out->args.vc.actions_n)
+ return len;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num >= ACTION_PROG_MAX_ARGS)
+ return -1;
+ prog_data->args_num++;
+
+ return len;
+}
+
+static int __prog_argument_name_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg_addr[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_len[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_data[ACTION_PROG_MAX_ARGS];
+ /* Calculate the base size based on the actual structure size */
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg_addr[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_addr[arg_ind].size = 0;
+
+ if (push_args(ctx, &arg_addr[arg_ind]))
+ return -1;
+ arg_len[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, length) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_len[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->length);
+
+ if (push_args(ctx, &arg_len[arg_ind]))
+ return -1;
+ arg_data[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_data[arg_ind].size =
+ sizeof(((struct action_prog_argument_data *)0)->name);
+
+ if (push_args(ctx, &arg_data[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int __prog_argument_size_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, size) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->size);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int __prog_argument_value_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, value) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->value);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int
+parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ RTE_SET_USED(size);
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (__prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return __prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+/**
+ * Convert action_prog_data to rte_flow_action_prog.
+ * Converts PROG actions from action_prog_data format to rte_flow_action_prog format.
+ *
+ * @param actions The flow actions array
+ * @param action_count Optional count of actions. If 0, will count until END action
+ * @return 0 on success, negative value on failure
+ */
+static int
+convert_action_prog_to_rte_flow(struct rte_flow_action *actions,
+ uint32_t prog_action_count,
+ uint32_t *converted_idx,
+ uint32_t converted_idx_cap,
+ uint32_t *converted_idx_num)
+{
+ uint32_t i = 0;
+ uint32_t j;
+ uint32_t k;
+ const struct action_prog_data *prog_data;
+ struct rte_flow_action_prog *prog;
+ struct rte_flow_action_prog_argument *args;
+ uint8_t *value;
+ bool arg_error;
+ int ret = 0;
+
+ if (converted_idx_num)
+ *converted_idx_num = 0;
+
+ /* If action_count is 0, count the actions until END action */
+ if (prog_action_count == 0) {
+ while (actions[i].type != RTE_FLOW_ACTION_TYPE_END)
+ i++;
+ prog_action_count = i + 1; /* Include END action */
+ i = 0; /* Reset counter for the processing loop */
+ }
+
+ /* Process all actions */
+ for (i = 0; i < prog_action_count; i++) {
+ if (actions[i].type == RTE_FLOW_ACTION_TYPE_PROG) {
+ prog_data = (const struct action_prog_data *)actions[i].conf;
+ if (!prog_data) {
+ fprintf(stderr, "Prog action found but no data provided\n");
+ ret = -EINVAL;
+ continue;
+ }
+
+ prog = calloc(1, sizeof(struct rte_flow_action_prog));
+ if (!prog) {
+ fprintf(stderr, "Failed to allocate memory for prog action\n");
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->name = strdup(prog_data->name);
+ if (!prog->name) {
+ fprintf(stderr, "Failed to allocate memory for prog name\n");
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->args_num = prog_data->args_num;
+
+ if (prog->args_num > 0) {
+ args = calloc(prog->args_num,
+ sizeof(struct rte_flow_action_prog_argument));
+ if (!args) {
+ fprintf(stderr,
+ "Failed to allocate memory for prog arguments\n");
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ arg_error = false;
+ for (j = 0; j < prog->args_num; j++) {
+ args[j].name = strdup(prog_data->args[j].name);
+ if (!args[j].name) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument name\n");
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ args[j].size = prog_data->args[j].size;
+ if (args[j].size == 0)
+ continue;
+ if (args[j].size > sizeof(prog_data->args[j].value)) {
+ free((void *)(uintptr_t)args[j].name);
+ arg_error = true;
+ ret = -EINVAL;
+ break;
+ }
+
+ value = malloc(args[j].size);
+ if (!value) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument value\n");
+ free((void *)(uintptr_t)args[j].name);
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ memcpy(value,
+ &prog_data->args[j].value,
+ args[j].size);
+ args[j].value = value;
+ }
+
+ if (arg_error) {
+ /* Free all allocated resources */
+ for (k = 0; k < j; k++) {
+ free((void *)(uintptr_t)args[k].name);
+ free((void *)(uintptr_t)args[k].value);
+ }
+ free(args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ continue;
+ }
+
+ prog->args = args;
+ }
+
+ actions[i].conf = prog;
+ if (converted_idx && converted_idx_num &&
+ *converted_idx_num < converted_idx_cap)
+ converted_idx[(*converted_idx_num)++] = i;
+ }
+ }
+
+ return ret;
+}
+
+static void
+free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num)
+{
+ uint32_t i;
+
+ for (i = 0; i < converted_idx_num; i++) {
+ uint32_t idx = converted_idx[i];
+ struct rte_flow_action_prog *prog;
+ uint32_t j;
+
+ if (actions[idx].type != RTE_FLOW_ACTION_TYPE_PROG)
+ continue;
+
+ prog = (struct rte_flow_action_prog *)(uintptr_t)actions[idx].conf;
+ if (!prog)
+ continue;
+
+ for (j = 0; j < prog->args_num; j++) {
+ free((void *)(uintptr_t)prog->args[j].name);
+ free((void *)(uintptr_t)prog->args[j].value);
+ }
+ free((void *)(uintptr_t)prog->args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ }
+}
+
static int
parse_vc_action_sample(struct context *ctx, const struct token *token,
const char *str, unsigned int len, void *buf,
@@ -13380,6 +13929,8 @@ indirect_action_list_conf_get(uint32_t conf_id)
static void
cmd_flow_parsed(const struct buffer *in)
{
+ int ret;
+
switch (in->command) {
case INFO:
port_flow_get_info(in->port);
@@ -13561,9 +14112,36 @@ cmd_flow_parsed(const struct buffer *in)
&in->args.vc.tunnel_ops);
break;
case CREATE:
- port_flow_create(in->port, &in->args.vc.attr,
- in->args.vc.pattern, in->args.vc.actions,
- &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ {
+ uint32_t *converted_idx;
+ uint32_t converted_idx_num = 0;
+
+ converted_idx = calloc(in->args.vc.actions_n,
+ sizeof(*converted_idx));
+ if (!converted_idx) {
+ fprintf(stderr,
+ "Warning: Failed to allocate conversion index list\n");
+ break;
+ }
+
+ /* Convert from action_prog_data to rte_flow_action_prog. */
+ ret = convert_action_prog_to_rte_flow(in->args.vc.actions,
+ 0,
+ converted_idx,
+ in->args.vc.actions_n,
+ &converted_idx_num);
+ if (ret < 0)
+ fprintf(stderr,
+ "Warning: Failed to convert program action data: %s\n",
+ strerror(-ret));
+ port_flow_create(in->port, &in->args.vc.attr,
+ in->args.vc.pattern, in->args.vc.actions,
+ &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ free_action_prog_converted(in->args.vc.actions,
+ converted_idx,
+ converted_idx_num);
+ free(converted_idx);
+ }
break;
case DESTROY:
port_flow_destroy(in->port, in->args.destroy.rule_n,
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-21 10:13 ` [PATCH v2] " Megha Ajmera
@ 2026-05-26 19:22 ` Stephen Hemminger
2026-05-27 9:20 ` Ajmera, Megha
2026-05-27 15:41 ` Stephen Hemminger
2026-06-01 4:16 ` [PATCH v3] " Megha Ajmera
2 siblings, 1 reply; 13+ messages in thread
From: Stephen Hemminger @ 2026-05-26 19:22 UTC (permalink / raw)
To: Megha Ajmera; +Cc: bruce.richardson, cristian.dumitrescu, praveen.shetty, dev
On Thu, 21 May 2026 15:43:54 +0530
Megha Ajmera <megha.ajmera@intel.com> wrote:
> Add parser support for a generic PROG flow action in testpmd.
>
> The update adds CLI tokens and parsing logic for program name and
> argument tuples (name, size, value), enabling programmable action
> configuration through the flow command interface.
>
> Example flow rule:
> flow create 0 ingress pattern eth / end actions prog name my_prog
> argument name arg0 size 4 value 10 / end
>
> Signed-off-by: Megha Ajmera <megha.ajmera@intel.com>
> Signed-off-by: Praveen Shetty <praveen.shetty@intel.com>
> ---
This looks like a third attempt to parse text into rte_flow.
Not sure how this fits in and why it would be useful?
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-26 19:22 ` Stephen Hemminger
@ 2026-05-27 9:20 ` Ajmera, Megha
2026-05-27 11:51 ` Konstantin Ananyev
0 siblings, 1 reply; 13+ messages in thread
From: Ajmera, Megha @ 2026-05-27 9:20 UTC (permalink / raw)
To: Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
>
> This looks like a third attempt to parse text into rte_flow.
> Not sure how this fits in and why it would be useful?
Thanks for the feedback.
I wanted to clarify the intent of this patch. The backend support for the PROG action already exists in the PMD. This patch is focused on adding the corresponding frontend (testpmd) parser support so that users can configure and exercise this functionality via CLI.
The motivation here is to enable configuration of programmable actions that are already supported in the backend but cannot currently be invoked through testpmd. With this addition, new actions that are exposed through PROG can be configured using the flow command interface.
Regards,
Megha
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-27 9:20 ` Ajmera, Megha
@ 2026-05-27 11:51 ` Konstantin Ananyev
2026-06-01 4:33 ` Ajmera, Megha
0 siblings, 1 reply; 13+ messages in thread
From: Konstantin Ananyev @ 2026-05-27 11:51 UTC (permalink / raw)
To: Ajmera, Megha, Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
> >
> > This looks like a third attempt to parse text into rte_flow.
> > Not sure how this fits in and why it would be useful?
>
> Thanks for the feedback.
> I wanted to clarify the intent of this patch. The backend support for the PROG
> action already exists in the PMD. This patch is focused on adding the
> corresponding frontend (testpmd) parser support so that users can configure
> and exercise this functionality via CLI.
> The motivation here is to enable configuration of programmable actions that are
> already supported in the backend but cannot currently be invoked through
> testpmd. With this addition, new actions that are exposed through PROG can be
> configured using the flow command interface.
For my own curiosity: how user can define his own PROG action?
Is it supposed to be programmed and uplodaded to the NIC by some external tool (P4 compiler)?
Or does it refer to the set of some predefined functions that given firmware supports?
Or ... ?
Sorry for probably naive questions, but I found is nearly zero information inside DPDK docs about
how PROG action supposed to work.
Thanks
Konstantin
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-21 10:13 ` [PATCH v2] " Megha Ajmera
2026-05-26 19:22 ` Stephen Hemminger
@ 2026-05-27 15:41 ` Stephen Hemminger
2026-06-01 4:27 ` Ajmera, Megha
2026-06-01 4:16 ` [PATCH v3] " Megha Ajmera
2 siblings, 1 reply; 13+ messages in thread
From: Stephen Hemminger @ 2026-05-27 15:41 UTC (permalink / raw)
To: Megha Ajmera; +Cc: bruce.richardson, cristian.dumitrescu, praveen.shetty, dev
On Thu, 21 May 2026 15:43:54 +0530
Megha Ajmera <megha.ajmera@intel.com> wrote:
> Add parser support for a generic PROG flow action in testpmd.
>
> The update adds CLI tokens and parsing logic for program name and
> argument tuples (name, size, value), enabling programmable action
> configuration through the flow command interface.
>
> Example flow rule:
> flow create 0 ingress pattern eth / end actions prog name my_prog
> argument name arg0 size 4 value 10 / end
>
> Signed-off-by: Megha Ajmera <megha.ajmera@intel.com>
> Signed-off-by: Praveen Shetty <praveen.shetty@intel.com>
> ---
Ran AI review on this manually since that gets better context.
The feedback was:
On Thu, 21 May 2026 15:43:54 +0530
Megha Ajmera <megha.ajmera@intel.com> wrote:
> Add parser support for a generic PROG flow action in testpmd.
>
> The update adds CLI tokens and parsing logic for program name and
> argument tuples (name, size, value), enabling programmable action
> configuration through the flow command interface.
Errors
1. CREATE proceeds with partially-converted action data on conversion
failure.
In cmd_flow_parsed() the CREATE case prints a warning if
convert_action_prog_to_rte_flow() returns negative, then
unconditionally calls port_flow_create(). Inside
convert_action_prog_to_rte_flow(), each per-PROG failure does
"continue" rather than aborting, so the function returns negative
while some actions[i].conf pointers have been replaced with
rte_flow_action_prog and others still point to the parser-internal
action_prog_data (a completely different layout). The PMD then
dereferences a mix of two unrelated structure types. Either abort
the create on negative ret, or make convert_action_prog_to_rte_flow()
fail-fast: free everything converted so far, restore conf pointers
(or never overwrite them until the whole pass succeeds), and return
without touching port_flow_create().
2. Argument value is sent in host byte order, but the API requires
network byte order.
The doc for struct rte_flow_action_prog_argument in
lib/ethdev/rte_flow.h says the value array must be in network byte
order. The conversion code does:
memcpy(value, &prog_data->args[j].value, args[j].size);
prog_data->args[j].value is a host-order uint64_t populated by
parse_int. For "size 4 value 10" on little-endian this produces
{0x0a,0x00,0x00,0x00}; on big-endian it produces {0x00,0x00,0x00,0x00}
-- the leading zero bytes of the 8-byte value, so the user always
sees zero. Either convert with rte_cpu_to_be_32/64 before memcpy,
or have the parser accept and store the value as a network-order
byte array of the declared size.
3. Only the CREATE path is converted; VALIDATE and the async/template
paths still pass action_prog_data to the PMD.
cmd_flow_parsed() routes the same in->args.vc.actions array to
VALIDATE (port_flow_validate), and to multiple async/template paths
(port_queue_flow_create, port_flow_template_table_create,
port_action_handle_create, etc.). None of these are converted by
this patch, so any PROG action used with "flow validate",
"flow queue ... create", indirect-action create, or pattern/action
templates passes the parser-internal action_prog_data to the PMD
with the wrong layout. Conversion needs to be factored into a
helper invoked from every code path that hands actions to the
ethdev API, not bolted onto CREATE only.
4. Unbounded scan for END in convert_action_prog_to_rte_flow().
while (actions[i].type != RTE_FLOW_ACTION_TYPE_END)
i++;
If a caller ever passes an actions array without a terminating END,
this walks off the end. The caller already knows
in->args.vc.actions_n; pass that as the bound and remove the
dual-mode behavior (count-or-not). The function is only ever called
with prog_action_count == 0, so the other branch is dead code
anyway.
5. size == 0 argument is silently accepted but violates the API.
The rte_flow_action_prog_argument doc states "its size must be
non-zero and its value must point to a valid array of size bytes".
The convert code does:
args[j].size = prog_data->args[j].size;
if (args[j].size == 0)
continue;
This leaves args[j].value == NULL while args[j].size == 0 and hands
that to the PMD. Either reject size == 0 at parse time, or treat it
as an error during conversion. Letting it through means
valid-looking input produces an API-contract violation that the PMD
has no obligation to handle.
Warnings
6. Identifiers with a leading double underscore are reserved.
__prog_argument_name_args_push, __prog_argument_size_args_push, and
__prog_argument_value_args_push use a "__" prefix. The C standard
reserves names starting with two underscores for the implementation.
Rename to e.g. prog_argument_name_args_push.
7. RTE_SET_USED on parameters that are used.
parse_vc_action_prog_argument() marks token and str as unused, then
passes both to parse_default(). parse_vc_action_prog_argument_value()
marks size as unused, then passes it to parse_vc_conf(). The v2
changelog says "Fixed compilation warning"; these spurious
RTE_SET_USED calls look like that fix. Remove them -- they mislead
future readers and can hide a real unused parameter if one is added
later.
8. Doxygen parameter name mismatch.
The comment block above convert_action_prog_to_rte_flow() documents
"@param action_count" but the actual parameter is named
prog_action_count. The same comment describes a dual-mode interface
("If 0, will count until END action") that is never exercised --
fix the name or simplify the interface.
9. No testpmd user-guide update.
This adds new CLI syntax
actions prog name <name> argument name <n> size <s> value <v>
but doc/guides/testpmd_app_ug/testpmd_funcs.rst is not updated.
Users have no documented way to learn the syntax.
10. No release notes update.
A new user-visible testpmd feature should have a one-line entry in
the current release notes.
Info
11. The static "struct arg arg[ACTION_PROG_MAX_ARGS]" arrays inside the
three __prog_argument_*_args_push helpers can simply be a single
non-static "struct arg" (or three) on the stack. push_args()
consumes the entries during the current parse_vc_conf() call;
persistent storage across CLI invocations is not needed and
obscures the lifetime.
12. action_prog_data.length is written by the COMMON_STRING parser for
the prog name field but never read afterwards (convert uses strdup,
relying on NUL termination). It is required as a destination for
the COMMON_STRING args triple, but a brief comment would prevent
the next reader from thinking it is meaningful state.
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3] app/test-pmd: add generic PROG action parser support
2026-05-21 10:13 ` [PATCH v2] " Megha Ajmera
2026-05-26 19:22 ` Stephen Hemminger
2026-05-27 15:41 ` Stephen Hemminger
@ 2026-06-01 4:16 ` Megha Ajmera
2 siblings, 0 replies; 13+ messages in thread
From: Megha Ajmera @ 2026-06-01 4:16 UTC (permalink / raw)
To: bruce.richardson, cristian.dumitrescu, praveen.shetty,
aman.deep.singh, dev
Add parser support for a generic PROG flow action in testpmd.
The update adds CLI tokens and parsing logic for program name and
argument tuples (name, size, value), enabling programmable action
configuration through the flow command interface.
Example flow rule:
flow create 0 ingress pattern eth / end actions prog name my_prog
argument name arg0 size 4 value 10 / end
Signed-off-by: Megha Ajmera <megha.ajmera@intel.com>
Signed-off-by: Praveen Shetty <praveen.shetty@intel.com>
---
v3:
* Addressed review comments from Stephen Hemminger.
v2:
* Fixed compilation warning.
app/test-pmd/cmdline_flow.c | 901 ++++++++++++++++++--
doc/guides/rel_notes/release_26_07.rst | 5 +
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 14 +
3 files changed, 859 insertions(+), 61 deletions(-)
diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index ebc036b14b..295b5ab58f 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -714,6 +714,13 @@ enum index {
ACTION_SET_META,
ACTION_SET_META_DATA,
ACTION_SET_META_MASK,
+ /* ACTION PROG */
+ ACTION_PROG,
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_SET_IPV6_DSCP,
@@ -981,6 +988,28 @@ struct action_sample_data {
struct rte_flow_action_sample conf;
uint32_t idx;
};
+
+#define ACTION_PROG_MAX_ARGS 32
+#define ACTION_PROG_NAME_LEN_MAX 64
+#define ACTION_PROG_ARG_NAME_LEN_MAX 16
+
+struct action_prog_argument_data {
+ char name[ACTION_PROG_ARG_NAME_LEN_MAX];
+ uint32_t length;
+ uint32_t size;
+ uint64_t value;
+};
+
+struct action_prog_data {
+ char name[ACTION_PROG_NAME_LEN_MAX];
+ uint32_t args_num;
+ /*
+ * COMMON_STRING parser stores name length here via parse_string() args
+ * triple. Conversion does not consume it beyond ensuring bounded write.
+ */
+ uint32_t length;
+ struct action_prog_argument_data args[ACTION_PROG_MAX_ARGS];
+};
/** Storage for struct rte_flow_action_sample. */
struct raw_sample_conf {
struct rte_flow_action data[ACTION_SAMPLE_ACTIONS_NUM];
@@ -2310,6 +2339,7 @@ static const enum index next_action[] = {
ACTION_RAW_DECAP,
ACTION_SET_TAG,
ACTION_SET_META,
+ ACTION_PROG,
ACTION_SET_IPV4_DSCP,
ACTION_SET_IPV6_DSCP,
ACTION_AGE,
@@ -2565,6 +2595,23 @@ static const enum index action_set_meta[] = {
ZERO,
};
+/** ACTION PROG */
+static const enum index next_action_prog[] = {
+ ACTION_PROG_NAME,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
+static const enum index next_prog_arg[] = {
+ ACTION_PROG_ARGUMENT_NAME,
+ ACTION_PROG_ARGUMENT_SIZE,
+ ACTION_PROG_ARGUMENT_VALUE,
+ ACTION_PROG_ARGUMENT,
+ ACTION_NEXT,
+ ZERO,
+};
+
static const enum index action_set_ipv4_dscp[] = {
ACTION_SET_IPV4_DSCP_VALUE,
ACTION_NEXT,
@@ -2803,6 +2850,27 @@ static int parse_vc_action_set_meta(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
unsigned int size);
+static int parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size);
+static int parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static void free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num);
static int parse_vc_action_sample(struct context *ctx,
const struct token *token, const char *str,
unsigned int len, void *buf,
@@ -7816,6 +7884,48 @@ static const struct token token_list[] = {
(struct rte_flow_action_set_meta, mask)),
.call = parse_vc_conf,
},
+ [ACTION_PROG] = {
+ .name = "prog",
+ .help = "Program action: action prog name <name> [argument ...]",
+ .priv = PRIV_ACTION(PROG,
+ sizeof(struct action_prog_data)),
+ .next = NEXT(next_action_prog),
+ .call = parse_vc_action_prog,
+ },
+ [ACTION_PROG_NAME] = {
+ .name = "name",
+ .help = "Action name",
+ .next = NEXT(next_action_prog, NEXT_ENTRY(COMMON_STRING)),
+ .args = ARGS(ARGS_ENTRY_ARB(0, 0),
+ ARGS_ENTRY(struct action_prog_data, length),
+ ARGS_ENTRY_ARB(0,
+ ACTION_PROG_NAME_LEN_MAX)),
+ .call = parse_vc_conf,
+ },
+ [ACTION_PROG_ARGUMENT] = {
+ .name = "argument",
+ .help = "Keyword: argument",
+ .next = NEXT(next_prog_arg),
+ .call = parse_vc_action_prog_argument,
+ },
+ [ACTION_PROG_ARGUMENT_NAME] = {
+ .name = "name",
+ .help = "Argument Name",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_STRING)),
+ .call = parse_vc_action_prog_argument_name,
+ },
+ [ACTION_PROG_ARGUMENT_SIZE] = {
+ .name = "size",
+ .help = "Argument size (bytes)",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_size,
+ },
+ [ACTION_PROG_ARGUMENT_VALUE] = {
+ .name = "value",
+ .help = "Argument value",
+ .next = NEXT(next_prog_arg, NEXT_ENTRY(COMMON_UNSIGNED)),
+ .call = parse_vc_action_prog_argument_value,
+ },
[ACTION_SET_IPV4_DSCP] = {
.name = "set_ipv4_dscp",
.help = "set DSCP value",
@@ -8408,7 +8518,7 @@ parse_init(struct context *ctx, const struct token *token,
struct buffer *out = buf;
/* Token name must match. */
- if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+ if (parse_default(ctx, token, str, len, buf, size) < 0)
return -1;
/* Nothing else to do if there is no buffer. */
if (!out)
@@ -8434,7 +8544,7 @@ parse_ia(struct context *ctx, const struct token *token,
struct buffer *out = buf;
/* Token name must match. */
- if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+ if (parse_default(ctx, token, str, len, buf, size) < 0)
return -1;
/* Nothing else to do if there is no buffer. */
if (!out)
@@ -10415,6 +10525,511 @@ parse_vc_action_set_meta(struct context *ctx, const struct token *token,
return len;
}
+/** Parse PROG action */
+static int
+parse_vc_action_prog(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ int ret;
+
+ ret = parse_vc(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return ret;
+
+ if (!out->args.vc.actions_n)
+ return -1;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ prog_data->args_num = 0;
+
+ return ret;
+}
+
+/** Called when args keyword is encountered */
+static int
+parse_vc_action_prog_argument(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+
+ /* Token name must match. */
+ if (parse_default(ctx, token, str, len, buf, size) < 0)
+ return -1;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ return len;
+
+ if (!out->args.vc.actions_n)
+ return len;
+
+ /* Point to selected object. */
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num >= ACTION_PROG_MAX_ARGS)
+ return -1;
+ prog_data->args_num++;
+
+ return len;
+}
+
+static int prog_argument_name_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg_addr[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_len[ACTION_PROG_MAX_ARGS];
+ static struct arg arg_data[ACTION_PROG_MAX_ARGS];
+ /* Calculate the base size based on the actual structure size */
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg_addr[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_addr[arg_ind].size = 0;
+
+ if (push_args(ctx, &arg_addr[arg_ind]))
+ return -1;
+ arg_len[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, length) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_len[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->length);
+
+ if (push_args(ctx, &arg_len[arg_ind]))
+ return -1;
+ arg_data[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, name) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg_data[arg_ind].size =
+ sizeof(((struct action_prog_argument_data *)0)->name);
+
+ if (push_args(ctx, &arg_data[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int prog_argument_size_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, size) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->size);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int prog_argument_value_args_push(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size, uint32_t arg_ind)
+{
+ static struct arg arg[ACTION_PROG_MAX_ARGS];
+ uint32_t prog_data_base_size = offsetof(struct action_prog_data, args);
+
+ RTE_SET_USED(token);
+ RTE_SET_USED(str);
+ RTE_SET_USED(len);
+ RTE_SET_USED(buf);
+ RTE_SET_USED(size);
+
+ arg[arg_ind].offset = prog_data_base_size +
+ offsetof(struct action_prog_argument_data, value) +
+ (arg_ind * sizeof(struct action_prog_argument_data));
+ arg[arg_ind].size = sizeof(((struct action_prog_argument_data *)0)->value);
+
+ if (push_args(ctx, &arg[arg_ind]))
+ return -1;
+
+ return 0;
+}
+
+static int
+parse_vc_action_prog_argument_name(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return prog_argument_name_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_size(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return prog_argument_size_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+static int
+parse_vc_action_prog_argument_value(struct context *ctx,
+ const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ struct action_prog_data *prog_data;
+ uint32_t arg_ind;
+ uint32_t max_arg_ind = (ACTION_PROG_MAX_ARGS - 1);
+ int ret;
+
+ ret = parse_vc_conf(ctx, token, str, len, buf, size);
+ if (ret < 0)
+ return ret;
+
+ /* Nothing else to do if there is no buffer. */
+ if (!out)
+ goto error_push;
+
+ if (!ctx->object)
+ goto error_push;
+
+ ctx->object = out->args.vc.data;
+ ctx->objmask = NULL;
+ prog_data = ctx->object;
+ if (prog_data->args_num == 0)
+ goto error_push;
+ arg_ind = prog_data->args_num - 1;
+ if (prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ arg_ind) < 0)
+ return -1;
+
+ return ret;
+
+error_push:
+ return prog_argument_value_args_push(ctx, token, str, len, buf, size,
+ max_arg_ind);
+}
+
+/**
+ * Convert action_prog_data to rte_flow_action_prog.
+ * Converts PROG actions from action_prog_data format to rte_flow_action_prog format.
+ *
+ * @param actions The flow actions array.
+ * @param prog_action_count Number of actions in @p actions array (bounded).
+ * @return 0 on success, negative value on failure.
+ */
+static int
+convert_action_prog_to_rte_flow(struct rte_flow_action *actions,
+ uint32_t prog_action_count,
+ uint32_t *converted_idx,
+ uint32_t converted_idx_cap,
+ uint32_t *converted_idx_num)
+{
+ uint32_t i = 0;
+ uint32_t j;
+ uint32_t k;
+ rte_be64_t be_value;
+ const struct action_prog_data *prog_data;
+ struct rte_flow_action_prog *prog;
+ struct rte_flow_action_prog_argument *args;
+ uint8_t *value;
+ bool arg_error;
+ int ret = 0;
+
+ if (converted_idx_num)
+ *converted_idx_num = 0;
+
+ /* Process all actions */
+ for (i = 0; i < prog_action_count; i++) {
+ if (actions[i].type == RTE_FLOW_ACTION_TYPE_PROG) {
+ prog_data = (const struct action_prog_data *)actions[i].conf;
+ if (!prog_data) {
+ fprintf(stderr, "Prog action found but no data provided\n");
+ ret = -EINVAL;
+ continue;
+ }
+
+ prog = calloc(1, sizeof(struct rte_flow_action_prog));
+ if (!prog) {
+ fprintf(stderr, "Failed to allocate memory for prog action\n");
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->name = strdup(prog_data->name);
+ if (!prog->name) {
+ fprintf(stderr, "Failed to allocate memory for prog name\n");
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ prog->args_num = prog_data->args_num;
+
+ if (prog->args_num > 0) {
+ args = calloc(prog->args_num,
+ sizeof(struct rte_flow_action_prog_argument));
+ if (!args) {
+ fprintf(stderr,
+ "Failed to allocate memory for prog arguments\n");
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ ret = -ENOMEM;
+ continue;
+ }
+
+ arg_error = false;
+ for (j = 0; j < prog->args_num; j++) {
+ args[j].name = strdup(prog_data->args[j].name);
+ if (!args[j].name) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument name\n");
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ args[j].size = prog_data->args[j].size;
+ if (args[j].size == 0) {
+ fprintf(stderr,
+ "Invalid PROG argument size 0 for '%s'\n",
+ args[j].name);
+ free((void *)(uintptr_t)args[j].name);
+ ret = -EINVAL;
+ arg_error = true;
+ break;
+ }
+ if (args[j].size > sizeof(prog_data->args[j].value)) {
+ free((void *)(uintptr_t)args[j].name);
+ arg_error = true;
+ ret = -EINVAL;
+ break;
+ }
+
+ value = malloc(args[j].size);
+ if (!value) {
+ fprintf(stderr,
+ "Failed to allocate memory for argument value\n");
+ free((void *)(uintptr_t)args[j].name);
+ ret = -ENOMEM;
+ arg_error = true;
+ break;
+ }
+
+ be_value = rte_cpu_to_be_64(prog_data->args[j].value);
+ memcpy(value,
+ (const uint8_t *)&be_value +
+ (sizeof(be_value) - args[j].size),
+ args[j].size);
+ args[j].value = value;
+ }
+
+ if (arg_error) {
+ /* Free all allocated resources */
+ for (k = 0; k < j; k++) {
+ free((void *)(uintptr_t)args[k].name);
+ free((void *)(uintptr_t)args[k].value);
+ }
+ free(args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ continue;
+ }
+
+ prog->args = args;
+ }
+
+ actions[i].conf = prog;
+ if (converted_idx && converted_idx_num &&
+ *converted_idx_num < converted_idx_cap)
+ converted_idx[(*converted_idx_num)++] = i;
+ }
+ }
+
+ return ret;
+}
+
+static void
+free_action_prog_converted(struct rte_flow_action *actions,
+ const uint32_t *converted_idx,
+ uint32_t converted_idx_num)
+{
+ uint32_t i;
+
+ for (i = 0; i < converted_idx_num; i++) {
+ uint32_t idx = converted_idx[i];
+ struct rte_flow_action_prog *prog;
+ uint32_t j;
+
+ if (actions[idx].type != RTE_FLOW_ACTION_TYPE_PROG)
+ continue;
+
+ prog = (struct rte_flow_action_prog *)(uintptr_t)actions[idx].conf;
+ if (!prog)
+ continue;
+
+ for (j = 0; j < prog->args_num; j++) {
+ free((void *)(uintptr_t)prog->args[j].name);
+ free((void *)(uintptr_t)prog->args[j].value);
+ }
+ free((void *)(uintptr_t)prog->args);
+ free((void *)(uintptr_t)prog->name);
+ free(prog);
+ }
+}
+
+struct action_prog_conv_ctx {
+ struct rte_flow_action *actions;
+ uint32_t *converted_idx;
+ uint32_t converted_idx_num;
+};
+
+static int
+prepare_action_prog_conversion(struct rte_flow_action *actions,
+ uint32_t actions_n,
+ const char *op_name,
+ struct action_prog_conv_ctx *ctx)
+{
+ int ret;
+
+ memset(ctx, 0, sizeof(*ctx));
+ if (!actions)
+ return 0;
+ if (actions_n == 0) {
+ fprintf(stderr,
+ "%s: invalid action count (0) for PROG conversion\n",
+ op_name);
+ return -EINVAL;
+ }
+
+ ctx->actions = actions;
+ ctx->converted_idx = calloc(actions_n, sizeof(*ctx->converted_idx));
+ if (!ctx->converted_idx) {
+ fprintf(stderr,
+ "%s: failed to allocate conversion index list\n",
+ op_name);
+ return -ENOMEM;
+ }
+
+ ret = convert_action_prog_to_rte_flow(actions, actions_n,
+ ctx->converted_idx, actions_n,
+ &ctx->converted_idx_num);
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: failed to convert program action data: %s\n",
+ op_name, strerror(-ret));
+ free_action_prog_converted(actions, ctx->converted_idx,
+ ctx->converted_idx_num);
+ free(ctx->converted_idx);
+ ctx->converted_idx = NULL;
+ ctx->converted_idx_num = 0;
+ ctx->actions = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void
+release_action_prog_conversion(struct action_prog_conv_ctx *ctx)
+{
+ if (!ctx->converted_idx)
+ return;
+
+ free_action_prog_converted(ctx->actions, ctx->converted_idx,
+ ctx->converted_idx_num);
+ free(ctx->converted_idx);
+ ctx->converted_idx = NULL;
+ ctx->converted_idx_num = 0;
+ ctx->actions = NULL;
+}
+
static int
parse_vc_action_sample(struct context *ctx, const struct token *token,
const char *str, unsigned int len, void *buf,
@@ -13326,10 +13941,17 @@ indirect_action_flow_conf_create(const struct buffer *in)
{
int len, ret;
uint32_t i;
+ struct action_prog_conv_ctx conv_ctx;
struct indlst_conf *indlst_conf = NULL;
size_t base = RTE_ALIGN(sizeof(*indlst_conf), 8);
struct rte_flow_action *src = in->args.vc.actions;
+ ret = prepare_action_prog_conversion(src, in->args.vc.actions_n,
+ "INDIRECT_ACTION_FLOW_CONF_CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ goto end;
+
if (!in->args.vc.actions_n)
goto end;
len = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0, src, NULL);
@@ -13356,6 +13978,7 @@ indirect_action_flow_conf_create(const struct buffer *in)
indlst_conf->conf[i] = indlst_conf->actions[i].conf;
SLIST_INSERT_HEAD(&indlst_conf_head, indlst_conf, next);
end:
+ release_action_prog_conversion(&conv_ctx);
if (indlst_conf)
printf("created indirect action list configuration %u\n",
in->args.vc.attr.group);
@@ -13380,6 +14003,8 @@ indirect_action_list_conf_get(uint32_t conf_id)
static void
cmd_flow_parsed(const struct buffer *in)
{
+ int ret;
+
switch (in->command) {
case INFO:
port_flow_get_info(in->port);
@@ -13407,15 +14032,26 @@ cmd_flow_parsed(const struct buffer *in)
in->args.templ_destroy.template_id);
break;
case ACTIONS_TEMPLATE_CREATE:
- port_flow_actions_template_create(in->port,
- in->args.vc.act_templ_id,
- &((const struct rte_flow_actions_template_attr) {
- .ingress = in->args.vc.attr.ingress,
- .egress = in->args.vc.attr.egress,
- .transfer = in->args.vc.attr.transfer,
- }),
- in->args.vc.actions,
- in->args.vc.masks);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "ACTIONS_TEMPLATE_CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_flow_actions_template_create(in->port,
+ in->args.vc.act_templ_id,
+ &((const struct rte_flow_actions_template_attr) {
+ .ingress = in->args.vc.attr.ingress,
+ .egress = in->args.vc.attr.egress,
+ .transfer = in->args.vc.attr.transfer,
+ }),
+ in->args.vc.actions,
+ in->args.vc.masks);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case ACTIONS_TEMPLATE_DESTROY:
port_flow_actions_template_destroy(in->port,
@@ -13438,18 +14074,40 @@ cmd_flow_parsed(const struct buffer *in)
(in->port, in->args.table_destroy.table_id[0]);
break;
case GROUP_SET_MISS_ACTIONS:
- port_queue_group_set_miss_actions(in->port, &in->args.vc.attr,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "GROUP_SET_MISS_ACTIONS",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_group_set_miss_actions(in->port, &in->args.vc.attr,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case TABLE_RESIZE:
port_flow_template_table_resize(in->port, in->args.table.id,
in->args.table.attr.nb_flows);
break;
case QUEUE_CREATE:
- port_queue_flow_create(in->port, in->queue, in->postpone,
- in->args.vc.table_id, in->args.vc.rule_id,
- in->args.vc.pat_templ_id, in->args.vc.act_templ_id,
- in->args.vc.pattern, in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "QUEUE_CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_flow_create(in->port, in->queue, in->postpone,
+ in->args.vc.table_id, in->args.vc.rule_id,
+ in->args.vc.pat_templ_id, in->args.vc.act_templ_id,
+ in->args.vc.pattern, in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case QUEUE_DESTROY:
port_queue_flow_destroy(in->port, in->queue, in->postpone,
@@ -13462,9 +14120,20 @@ cmd_flow_parsed(const struct buffer *in)
in->args.destroy.rule[0]);
break;
case QUEUE_UPDATE:
- port_queue_flow_update(in->port, in->queue, in->postpone,
- in->args.vc.rule_id, in->args.vc.act_templ_id,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "QUEUE_UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_flow_update(in->port, in->queue, in->postpone,
+ in->args.vc.rule_id, in->args.vc.act_templ_id,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case PUSH:
port_queue_flow_push(in->port, in->queue);
@@ -13487,16 +14156,27 @@ cmd_flow_parsed(const struct buffer *in)
break;
case QUEUE_INDIRECT_ACTION_CREATE:
case QUEUE_INDIRECT_ACTION_LIST_CREATE:
- port_queue_action_handle_create(
- in->port, in->queue, in->postpone,
- in->args.vc.attr.group,
- in->command == QUEUE_INDIRECT_ACTION_LIST_CREATE,
- &((const struct rte_flow_indir_action_conf) {
- .ingress = in->args.vc.attr.ingress,
- .egress = in->args.vc.attr.egress,
- .transfer = in->args.vc.attr.transfer,
- }),
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "QUEUE_INDIRECT_ACTION_CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_action_handle_create(
+ in->port, in->queue, in->postpone,
+ in->args.vc.attr.group,
+ in->command == QUEUE_INDIRECT_ACTION_LIST_CREATE,
+ &((const struct rte_flow_indir_action_conf) {
+ .ingress = in->args.vc.attr.ingress,
+ .egress = in->args.vc.attr.egress,
+ .transfer = in->args.vc.attr.transfer,
+ }),
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case QUEUE_INDIRECT_ACTION_DESTROY:
port_queue_action_handle_destroy(in->port,
@@ -13505,10 +14185,21 @@ cmd_flow_parsed(const struct buffer *in)
in->args.ia_destroy.action_id);
break;
case QUEUE_INDIRECT_ACTION_UPDATE:
- port_queue_action_handle_update(in->port,
- in->queue, in->postpone,
- in->args.vc.attr.group,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "QUEUE_INDIRECT_ACTION_UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_action_handle_update(in->port,
+ in->queue, in->postpone,
+ in->args.vc.attr.group,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case QUEUE_INDIRECT_ACTION_QUERY:
port_queue_action_handle_query(in->port,
@@ -13516,23 +14207,45 @@ cmd_flow_parsed(const struct buffer *in)
in->args.ia.action_id);
break;
case QUEUE_INDIRECT_ACTION_QUERY_UPDATE:
- port_queue_action_handle_query_update(in->port, in->queue,
- in->postpone,
- in->args.ia.action_id,
- in->args.ia.qu_mode,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "QUEUE_INDIRECT_ACTION_QUERY_UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_queue_action_handle_query_update(in->port, in->queue,
+ in->postpone,
+ in->args.ia.action_id,
+ in->args.ia.qu_mode,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case INDIRECT_ACTION_CREATE:
case INDIRECT_ACTION_LIST_CREATE:
- port_action_handle_create(
- in->port, in->args.vc.attr.group,
- in->command == INDIRECT_ACTION_LIST_CREATE,
- &((const struct rte_flow_indir_action_conf) {
- .ingress = in->args.vc.attr.ingress,
- .egress = in->args.vc.attr.egress,
- .transfer = in->args.vc.attr.transfer,
- }),
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "INDIRECT_ACTION_CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_action_handle_create(
+ in->port, in->args.vc.attr.group,
+ in->command == INDIRECT_ACTION_LIST_CREATE,
+ &((const struct rte_flow_indir_action_conf) {
+ .ingress = in->args.vc.attr.ingress,
+ .egress = in->args.vc.attr.egress,
+ .transfer = in->args.vc.attr.transfer,
+ }),
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case INDIRECT_ACTION_FLOW_CONF_CREATE:
indirect_action_flow_conf_create(in);
@@ -13543,27 +14256,71 @@ cmd_flow_parsed(const struct buffer *in)
in->args.ia_destroy.action_id);
break;
case INDIRECT_ACTION_UPDATE:
- port_action_handle_update(in->port, in->args.vc.attr.group,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "INDIRECT_ACTION_UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_action_handle_update(in->port, in->args.vc.attr.group,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case INDIRECT_ACTION_QUERY:
port_action_handle_query(in->port, in->args.ia.action_id);
break;
case INDIRECT_ACTION_QUERY_UPDATE:
- port_action_handle_query_update(in->port,
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "INDIRECT_ACTION_QUERY_UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_action_handle_query_update(in->port,
in->args.ia.action_id,
in->args.ia.qu_mode,
in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case VALIDATE:
- port_flow_validate(in->port, &in->args.vc.attr,
- in->args.vc.pattern, in->args.vc.actions,
- &in->args.vc.tunnel_ops);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "VALIDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_flow_validate(in->port, &in->args.vc.attr,
+ in->args.vc.pattern, in->args.vc.actions,
+ &in->args.vc.tunnel_ops);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case CREATE:
- port_flow_create(in->port, &in->args.vc.attr,
- in->args.vc.pattern, in->args.vc.actions,
- &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "CREATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_flow_create(in->port, &in->args.vc.attr,
+ in->args.vc.pattern, in->args.vc.actions,
+ &in->args.vc.tunnel_ops, in->args.vc.user_id);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case DESTROY:
port_flow_destroy(in->port, in->args.destroy.rule_n,
@@ -13571,8 +14328,19 @@ cmd_flow_parsed(const struct buffer *in)
in->args.destroy.is_user_id);
break;
case UPDATE:
- port_flow_update(in->port, in->args.vc.rule_id,
- in->args.vc.actions, in->args.vc.user_id);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "UPDATE",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_flow_update(in->port, in->args.vc.rule_id,
+ in->args.vc.actions, in->args.vc.user_id);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case FLUSH:
port_flow_flush(in->port);
@@ -13608,8 +14376,19 @@ cmd_flow_parsed(const struct buffer *in)
port_flow_tunnel_list(in->port);
break;
case ACTION_POL_G:
- port_meter_policy_add(in->port, in->args.policy.policy_id,
- in->args.vc.actions);
+ {
+ struct action_prog_conv_ctx conv_ctx;
+
+ ret = prepare_action_prog_conversion(in->args.vc.actions,
+ in->args.vc.actions_n,
+ "ACTION_POL_G",
+ &conv_ctx);
+ if (ret < 0)
+ break;
+ port_meter_policy_add(in->port, in->args.policy.policy_id,
+ in->args.vc.actions);
+ release_action_prog_conversion(&conv_ctx);
+ }
break;
case FLEX_ITEM_CREATE:
flex_item_create(in->port, in->args.flex.token,
diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index f012d47a4b..12aeddf7f3 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -63,6 +63,11 @@ New Features
``rte_eal_init`` and the application is responsible for probing each device,
* ``--auto-probing`` enables the initial bus probing, which is the current default behavior.
+* **Added PROG action parsing in testpmd flow CLI.**
+
+ Added support for ``actions prog name <name> argument name <n> size <s> value <v>``
+ syntax in testpmd flow commands.
+
Removed Items
-------------
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index d6c344bfb3..913dc6d524 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -4321,6 +4321,20 @@ This section lists supported actions and their attributes, if any.
- ``type {unsigned}``: NAT64 translation type
+- ``prog``: execute a program action.
+
+ - ``name {string}``: action name.
+ - ``argument``: start a program argument definition, may be repeated.
+ - ``name {string}``: argument name.
+ - ``size {unsigned}``: argument size in bytes (non-zero, up to 8 bytes).
+ - ``value {unsigned}``: argument value serialized in network byte order
+ using the configured ``size``.
+
+ Example::
+
+ testpmd> flow create 0 ingress pattern eth / ipv4 / end actions \
+ prog name foo argument name a size 4 value 10 / end
+
Destroying flow rules
~~~~~~~~~~~~~~~~~~~~~
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-27 15:41 ` Stephen Hemminger
@ 2026-06-01 4:27 ` Ajmera, Megha
0 siblings, 0 replies; 13+ messages in thread
From: Ajmera, Megha @ 2026-06-01 4:27 UTC (permalink / raw)
To: Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
>
> Ran AI review on this manually since that gets better context.
> The feedback was:
>
Thanks for the review. I have addressed most of the comments in v3 patch.
Comment #11 is intentionally not applied. In the PROG argument parser path, push_args() stores pointers to struct arg entries in ctx->args, and those entries are consumed later by subsequent parser callbacks (parse_int() / parse_string()), not within the same helper call. So if the helper used stack-local struct arg, those pointers would become invalid after return (use-after-scope).
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-05-27 11:51 ` Konstantin Ananyev
@ 2026-06-01 4:33 ` Ajmera, Megha
2026-06-01 5:07 ` Stephen Hemminger
2026-06-02 7:35 ` Konstantin Ananyev
0 siblings, 2 replies; 13+ messages in thread
From: Ajmera, Megha @ 2026-06-01 4:33 UTC (permalink / raw)
To: Konstantin Ananyev, Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
>
> For my own curiosity: how user can define his own PROG action?
> Is it supposed to be programmed and uplodaded to the NIC by some external tool
> (P4 compiler)?
> Or does it refer to the set of some predefined functions that given firmware
> supports?
> Or ... ?
> Sorry for probably naive questions, but I found is nearly zero information inside
> DPDK docs about how PROG action supposed to work.
PROG in rte_flow is currently a vendor-defined action interface (name + arguments), not a generic DPDK programming model by itself.
So in practice:
1. DPDK/testpmd does not define or upload user code to NIC on its own.
2. testpmd just passes the prog name/argument/payload to the PMD via rte_flow.
3. What that name/arguments means is NIC/firmware specific.
4. Depending on device support, it may map to predefined firmware-exposed functions
5. If the PMD/firmware does not recognize it, flow validate/create will fail.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-06-01 4:33 ` Ajmera, Megha
@ 2026-06-01 5:07 ` Stephen Hemminger
2026-06-02 7:35 ` Konstantin Ananyev
1 sibling, 0 replies; 13+ messages in thread
From: Stephen Hemminger @ 2026-06-01 5:07 UTC (permalink / raw)
To: Ajmera, Megha
Cc: Konstantin Ananyev, Richardson, Bruce, Dumitrescu, Cristian,
Shetty, Praveen, Singh, Aman Deep, dev@dpdk.org
On Mon, 1 Jun 2026 04:33:42 +0000
"Ajmera, Megha" <megha.ajmera@intel.com> wrote:
> >
> > For my own curiosity: how user can define his own PROG action?
> > Is it supposed to be programmed and uplodaded to the NIC by some external tool
> > (P4 compiler)?
> > Or does it refer to the set of some predefined functions that given firmware
> > supports?
> > Or ... ?
> > Sorry for probably naive questions, but I found is nearly zero information inside
> > DPDK docs about how PROG action supposed to work.
>
> PROG in rte_flow is currently a vendor-defined action interface (name + arguments), not a generic DPDK programming model by itself.
Supporting vendor specific extensions like this should be actively discouraged.
There is a reason DPDK succeeded and OpenDataplane did not.
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-06-01 4:33 ` Ajmera, Megha
2026-06-01 5:07 ` Stephen Hemminger
@ 2026-06-02 7:35 ` Konstantin Ananyev
2026-06-02 17:11 ` Ajmera, Megha
1 sibling, 1 reply; 13+ messages in thread
From: Konstantin Ananyev @ 2026-06-02 7:35 UTC (permalink / raw)
To: Ajmera, Megha, Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
> > For my own curiosity: how user can define his own PROG action?
> > Is it supposed to be programmed and uplodaded to the NIC by some external
> tool
> > (P4 compiler)?
> > Or does it refer to the set of some predefined functions that given firmware
> > supports?
> > Or ... ?
> > Sorry for probably naive questions, but I found is nearly zero information inside
> > DPDK docs about how PROG action supposed to work.
>
> PROG in rte_flow is currently a vendor-defined action interface (name +
> arguments), not a generic DPDK programming model by itself.
>
> So in practice:
>
> 1. DPDK/testpmd does not define or upload user code to NIC on its own.
> 2. testpmd just passes the prog name/argument/payload to the PMD via
> rte_flow.
> 3. What that name/arguments means is NIC/firmware specific.
> 4. Depending on device support, it may map to predefined firmware-exposed
> functions
Ok, so it is predefined by the FW.
Then shouldn't CPFL PG contain a list of supported PROGs and their arguments?
Again, some tests/examples for it with testpmd/DTS.
Or that's all will be the next step (patch-series)?
> 5. If the PMD/firmware does not recognize it, flow validate/create will fail.
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-06-02 7:35 ` Konstantin Ananyev
@ 2026-06-02 17:11 ` Ajmera, Megha
2026-06-03 6:53 ` Konstantin Ananyev
0 siblings, 1 reply; 13+ messages in thread
From: Ajmera, Megha @ 2026-06-02 17:11 UTC (permalink / raw)
To: Konstantin Ananyev, Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
>
> Ok, so it is predefined by the FW.
> Then shouldn't CPFL PG contain a list of supported PROGs and their arguments?
> Again, some tests/examples for it with testpmd/DTS.
> Or that's all will be the next step (patch-series)?
>
For now, this can be independent patch since it only enable parsing in test-pmd.
With this change one can use and test PROG actions for PMDs.
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH v2] app/test-pmd: add generic PROG action parser support
2026-06-02 17:11 ` Ajmera, Megha
@ 2026-06-03 6:53 ` Konstantin Ananyev
0 siblings, 0 replies; 13+ messages in thread
From: Konstantin Ananyev @ 2026-06-03 6:53 UTC (permalink / raw)
To: Ajmera, Megha, Stephen Hemminger
Cc: Richardson, Bruce, Dumitrescu, Cristian, Shetty, Praveen,
Singh, Aman Deep, dev@dpdk.org
> > Ok, so it is predefined by the FW.
> > Then shouldn't CPFL PG contain a list of supported PROGs and their arguments?
> > Again, some tests/examples for it with testpmd/DTS.
> > Or that's all will be the next step (patch-series)?
> >
> For now, this can be independent patch since it only enable parsing in test-pmd.
> With this change one can use and test PROG actions for PMDs.
Well, right now there is simply no way for the user to test/try this functionality.
Usual requirement for introducing new HW/SW feature for DPDK:
1) provide at least one implementation (exist, cpfl)
2) provide some test cases for it (app/test-pmd, app/test, ...)
3) document it properly - what prog/args are supported for each HW, etc.
#2 and #3 are missing completely - which means that for DPDK community
that functionality is sort of dead code.
TBH, I think it shouldn't be accepted at all at first place in such incomplete stage.
Konstantin
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-06-03 6:53 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-21 5:56 [PATCH] app/test-pmd: add generic PROG action parser support Megha Ajmera
2026-05-21 10:13 ` [PATCH v2] " Megha Ajmera
2026-05-26 19:22 ` Stephen Hemminger
2026-05-27 9:20 ` Ajmera, Megha
2026-05-27 11:51 ` Konstantin Ananyev
2026-06-01 4:33 ` Ajmera, Megha
2026-06-01 5:07 ` Stephen Hemminger
2026-06-02 7:35 ` Konstantin Ananyev
2026-06-02 17:11 ` Ajmera, Megha
2026-06-03 6:53 ` Konstantin Ananyev
2026-05-27 15:41 ` Stephen Hemminger
2026-06-01 4:27 ` Ajmera, Megha
2026-06-01 4:16 ` [PATCH v3] " Megha Ajmera
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox