This patch adds shared action support to testpmd CLI.
All shared actions created via testpmd CLI assigned ID for further
reference in other CLI commands. Shared action ID supplied as CLI
argument or assigned by testpmd is similar to flow ID & limited to
scope of testpdm CLI.
Create shared action syntax:
flow shared_action {port_id} create [action_id {shared_action_id}]
[ingress] [egress] action {action} / end
Create shared action examples:
flow shared_action 0 create action_id 100 \
ingress action rss queues 1 2 end / end
This creates shared rss action with id 100 on port 0.
flow shared_action 0 create action_id \
ingress action rss queues 0 1 end / end
This creates shared rss action with id assigned by testpmd
on port 0.
Update shared action syntax:
flow shared_action {port_id} update {shared_action_id}
action {action} / end
Update shared action example:
flow shared_action 0 update 100 \
action rss queues 0 3 end / end
This updates shared rss action having id 100 on port 0
with rss to queues 0 3 (in create example rss queues were
1 & 2).
Destroy shared action syntax:
flow shared_action {port_id} destroy action_id {shared_action_id} [...]
Destroy shared action example:
flow shared_action 0 destroy action_id 100 action_id 101
This destroys shared actions having id 100 & 101
Query shared action syntax:
flow shared_action {port} query {shared_action_id}
Query shared action example:
flow shared_action 0 query 100
This queries shared actions having id 100
Use shared action as flow action syntax:
flow create {port_id} ... / end actions [action / [...]]
shared {action_id} / [action / [...]] end
Use shared action as flow action example:
flow create 0 ingress pattern ... / end \
actions shared 100 / end
This creates flow rule where rss action is shared rss action
having id 100.
All shared action CLIs report status of the command.
Shared action query CLI output depends on action type.
Signed-off-by: Andrey Vesnovaty <andreyv@nvidia.com>
Acked-by: Ori Kam <orika@nvidia.com>
Acked-by: Ajit Khaparde <ajit.khaparde@broadcom.com>
" List and destroy aged flows"
" flow rules\n\n"
+ "flow shared_action {port_id} create"
+ " [action_id {shared_action_id}]"
+ " [ingress] [egress]"
+ " action {action} / end\n"
+ " Create shared action.\n\n"
+
+ "flow shared_action {port_id} update"
+ " {shared_action_id} action {action} / end\n"
+ " Update shared action.\n\n"
+
+ "flow shared_action {port_id} destroy"
+ " action_id {shared_action_id} [...]\n"
+ " Destroy specific shared actions.\n\n"
+
+ "flow shared_action {port_id} query"
+ " {shared_action_id}\n"
+ " Query an existing shared action.\n\n"
+
"set vxlan ip-version (ipv4|ipv6) vni (vni) udp-src"
" (udp-src) udp-dst (udp-dst) ip-src (ip-src) ip-dst"
" (ip-dst) eth-src (eth-src) eth-dst (eth-dst)\n"
PORT_ID,
GROUP_ID,
PRIORITY_LEVEL,
+ SHARED_ACTION_ID,
/* Top-level command. */
SET,
/* Top-level command. */
FLOW,
/* Sub-level commands. */
+ SHARED_ACTION,
VALIDATE,
CREATE,
DESTROY,
EGRESS,
TRANSFER,
+ /* Shared action arguments */
+ SHARED_ACTION_CREATE,
+ SHARED_ACTION_UPDATE,
+ SHARED_ACTION_DESTROY,
+ SHARED_ACTION_QUERY,
+
+ /* Shared action create arguments */
+ SHARED_ACTION_CREATE_ID,
+ SHARED_ACTION_INGRESS,
+ SHARED_ACTION_EGRESS,
+ SHARED_ACTION_SPEC,
+
+ /* Shared action destroy arguments */
+ SHARED_ACTION_DESTROY_ID,
+
/* Validate/create pattern. */
PATTERN,
ITEM_PARAM_IS,
ACTION_SAMPLE_RATIO,
ACTION_SAMPLE_INDEX,
ACTION_SAMPLE_INDEX_VALUE,
+ ACTION_SHARED,
+ SHARED_ACTION_ID2PTR,
};
/** Maximum size for pattern in struct rte_flow_item_raw. */
enum index command; /**< Flow command. */
portid_t port; /**< Affected port ID. */
union {
+ struct {
+ uint32_t *action_id;
+ uint32_t action_id_n;
+ } sa_destroy; /**< Shared action destroy arguments. */
+ struct {
+ uint32_t action_id;
+ } sa; /* Shared action query arguments */
struct {
struct rte_flow_attr attr;
struct rte_flow_item *pattern;
.size = s, \
})
+static const enum index next_sa_create_attr[] = {
+ SHARED_ACTION_CREATE_ID,
+ SHARED_ACTION_INGRESS,
+ SHARED_ACTION_EGRESS,
+ SHARED_ACTION_SPEC,
+ ZERO,
+};
+
+static const enum index next_sa_subcmd[] = {
+ SHARED_ACTION_CREATE,
+ SHARED_ACTION_UPDATE,
+ SHARED_ACTION_DESTROY,
+ SHARED_ACTION_QUERY,
+ ZERO,
+};
+
static const enum index next_vc_attr[] = {
GROUP,
PRIORITY,
ZERO,
};
+static const enum index next_sa_destroy_attr[] = {
+ SHARED_ACTION_DESTROY_ID,
+ END,
+ ZERO,
+};
+
static const enum index item_param[] = {
ITEM_PARAM_IS,
ITEM_PARAM_SPEC,
ACTION_SET_IPV6_DSCP,
ACTION_AGE,
ACTION_SAMPLE,
+ ACTION_SHARED,
ZERO,
};
static int parse_port(struct context *, const struct token *,
const char *, unsigned int,
void *, unsigned int);
+static int parse_sa(struct context *, const struct token *,
+ const char *, unsigned int,
+ void *, unsigned int);
+static int parse_sa_destroy(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size);
+static int parse_sa_id2ptr(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len, void *buf,
+ unsigned int size);
static int comp_none(struct context *, const struct token *,
unsigned int, char *, unsigned int);
static int comp_boolean(struct context *, const struct token *,
.call = parse_int,
.comp = comp_none,
},
+ [SHARED_ACTION_ID] = {
+ .name = "{shared_action_id}",
+ .type = "SHARED_ACTION_ID",
+ .help = "shared action id",
+ .call = parse_int,
+ .comp = comp_none,
+ },
/* Top-level command. */
[FLOW] = {
.name = "flow",
.type = "{command} {port_id} [{arg} [...]]",
.help = "manage ingress/egress flow rules",
.next = NEXT(NEXT_ENTRY
- (VALIDATE,
+ (SHARED_ACTION,
+ VALIDATE,
CREATE,
DESTROY,
FLUSH,
ISOLATE)),
.call = parse_init,
},
+ /* Top-level command. */
+ [SHARED_ACTION] = {
+ .name = "shared_action",
+ .type = "{command} {port_id} [{arg} [...]]",
+ .help = "manage shared actions",
+ .next = NEXT(next_sa_subcmd, NEXT_ENTRY(PORT_ID)),
+ .args = ARGS(ARGS_ENTRY(struct buffer, port)),
+ .call = parse_sa,
+ },
/* Sub-level commands. */
+ [SHARED_ACTION_CREATE] = {
+ .name = "create",
+ .help = "create shared action",
+ .next = NEXT(next_sa_create_attr),
+ .call = parse_sa,
+ },
+ [SHARED_ACTION_UPDATE] = {
+ .name = "update",
+ .help = "update shared action",
+ .next = NEXT(NEXT_ENTRY(SHARED_ACTION_SPEC),
+ NEXT_ENTRY(SHARED_ACTION_ID)),
+ .args = ARGS(ARGS_ENTRY(struct buffer, args.vc.attr.group)),
+ .call = parse_sa,
+ },
+ [SHARED_ACTION_DESTROY] = {
+ .name = "destroy",
+ .help = "destroy shared action",
+ .next = NEXT(NEXT_ENTRY(SHARED_ACTION_DESTROY_ID)),
+ .args = ARGS(ARGS_ENTRY(struct buffer, port)),
+ .call = parse_sa_destroy,
+ },
+ [SHARED_ACTION_QUERY] = {
+ .name = "query",
+ .help = "query shared action",
+ .next = NEXT(NEXT_ENTRY(END), NEXT_ENTRY(SHARED_ACTION_ID)),
+ .args = ARGS(ARGS_ENTRY(struct buffer, args.sa.action_id)),
+ .call = parse_sa,
+ },
[VALIDATE] = {
.name = "validate",
.help = "check whether a flow rule can be created",
.call = parse_vc_action_sample_index,
.comp = comp_set_sample_index,
},
+ /* Shared action destroy arguments. */
+ [SHARED_ACTION_DESTROY_ID] = {
+ .name = "action_id",
+ .help = "specify a shared action id to destroy",
+ .next = NEXT(next_sa_destroy_attr,
+ NEXT_ENTRY(SHARED_ACTION_ID)),
+ .args = ARGS(ARGS_ENTRY_PTR(struct buffer,
+ args.sa_destroy.action_id)),
+ .call = parse_sa_destroy,
+ },
+ /* Shared action create arguments. */
+ [SHARED_ACTION_CREATE_ID] = {
+ .name = "action_id",
+ .help = "specify a shared action id to create",
+ .next = NEXT(next_sa_create_attr,
+ NEXT_ENTRY(SHARED_ACTION_ID)),
+ .args = ARGS(ARGS_ENTRY(struct buffer, args.vc.attr.group)),
+ },
+ [ACTION_SHARED] = {
+ .name = "shared",
+ .help = "apply shared action by id",
+ .priv = PRIV_ACTION(SHARED, 0),
+ .next = NEXT(NEXT_ENTRY(SHARED_ACTION_ID2PTR)),
+ .args = ARGS(ARGS_ENTRY_ARB(0, sizeof(uint32_t))),
+ .call = parse_vc,
+ },
+ [SHARED_ACTION_ID2PTR] = {
+ .name = "{action_id}",
+ .type = "SHARED_ACTION_ID",
+ .help = "shared action id",
+ .next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+ .call = parse_sa_id2ptr,
+ .comp = comp_none,
+ },
+ [SHARED_ACTION_INGRESS] = {
+ .name = "ingress",
+ .help = "affect rule to ingress",
+ .next = NEXT(next_sa_create_attr),
+ .call = parse_sa,
+ },
+ [SHARED_ACTION_EGRESS] = {
+ .name = "egress",
+ .help = "affect rule to egress",
+ .next = NEXT(next_sa_create_attr),
+ .call = parse_sa,
+ },
+ [SHARED_ACTION_SPEC] = {
+ .name = "action",
+ .help = "specify action to share",
+ .next = NEXT(next_action),
+ },
};
/** Remove and return last entry from argument stack. */
return len;
}
+/** Parse tokens for shared action commands. */
+static int
+parse_sa(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+
+ /* 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->command) {
+ if (ctx->curr != SHARED_ACTION)
+ return -1;
+ if (sizeof(*out) > size)
+ return -1;
+ out->command = ctx->curr;
+ ctx->objdata = 0;
+ ctx->object = out;
+ ctx->objmask = NULL;
+ out->args.vc.data = (uint8_t *)out + size;
+ return len;
+ }
+ switch (ctx->curr) {
+ case SHARED_ACTION_CREATE:
+ case SHARED_ACTION_UPDATE:
+ out->args.vc.actions =
+ (void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+ sizeof(double));
+ out->args.vc.attr.group = UINT32_MAX;
+ /* fallthrough */
+ case SHARED_ACTION_QUERY:
+ out->command = ctx->curr;
+ ctx->objdata = 0;
+ ctx->object = out;
+ ctx->objmask = NULL;
+ return len;
+ case SHARED_ACTION_EGRESS:
+ out->args.vc.attr.egress = 1;
+ return len;
+ case SHARED_ACTION_INGRESS:
+ out->args.vc.attr.ingress = 1;
+ return len;
+ default:
+ return -1;
+ }
+}
+
+
+/** Parse tokens for shared action destroy command. */
+static int
+parse_sa_destroy(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct buffer *out = buf;
+ uint32_t *action_id;
+
+ /* 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->command || out->command == SHARED_ACTION) {
+ if (ctx->curr != SHARED_ACTION_DESTROY)
+ return -1;
+ if (sizeof(*out) > size)
+ return -1;
+ out->command = ctx->curr;
+ ctx->objdata = 0;
+ ctx->object = out;
+ ctx->objmask = NULL;
+ out->args.sa_destroy.action_id =
+ (void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+ sizeof(double));
+ return len;
+ }
+ action_id = out->args.sa_destroy.action_id
+ + out->args.sa_destroy.action_id_n++;
+ if ((uint8_t *)action_id > (uint8_t *)out + size)
+ return -1;
+ ctx->objdata = 0;
+ ctx->object = action_id;
+ ctx->objmask = NULL;
+ return len;
+}
+
/** Parse tokens for validate/create commands. */
static int
parse_vc(struct context *ctx, const struct token *token,
return ret;
}
+static int
+parse_sa_id2ptr(struct context *ctx, const struct token *token,
+ const char *str, unsigned int len,
+ void *buf, unsigned int size)
+{
+ struct rte_flow_action *action = ctx->object;
+ uint32_t id;
+ int ret;
+
+ (void)buf;
+ (void)size;
+ ctx->objdata = 0;
+ ctx->object = &id;
+ ctx->objmask = NULL;
+ ret = parse_int(ctx, token, str, len, ctx->object, sizeof(id));
+ ctx->object = action;
+ if (ret != (int)len)
+ return ret;
+ /* set shared action */
+ if (action) {
+ action->conf = port_shared_action_get_by_id(ctx->port, id);
+ ret = (action->conf) ? ret : -1;
+ }
+ return ret;
+}
+
/** Parse set command, initialize output buffer for subsequent tokens. */
static int
parse_set_raw_encap_decap(struct context *ctx, const struct token *token,
cmd_flow_parsed(const struct buffer *in)
{
switch (in->command) {
+ case SHARED_ACTION_CREATE:
+ port_shared_action_create(
+ in->port, in->args.vc.attr.group,
+ &((const struct rte_flow_shared_action_conf) {
+ .ingress = in->args.vc.attr.ingress,
+ .egress = in->args.vc.attr.egress,
+ }),
+ in->args.vc.actions);
+ break;
+ case SHARED_ACTION_DESTROY:
+ port_shared_action_destroy(in->port,
+ in->args.sa_destroy.action_id_n,
+ in->args.sa_destroy.action_id);
+ break;
+ case SHARED_ACTION_UPDATE:
+ port_shared_action_update(in->port, in->args.vc.attr.group,
+ in->args.vc.actions);
+ break;
+ case SHARED_ACTION_QUERY:
+ port_shared_action_query(in->port, in->args.sa.action_id);
+ break;
case VALIDATE:
port_flow_validate(in->port, &in->args.vc.attr,
in->args.vc.pattern, in->args.vc.actions);
}
}
+static struct port_shared_action *
+action_get_by_id(portid_t port_id, uint32_t id)
+{
+ struct rte_port *port;
+ struct port_shared_action **ppsa;
+ struct port_shared_action *psa = NULL;
+
+ if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+ port_id == (portid_t)RTE_PORT_ALL)
+ return NULL;
+ port = &ports[port_id];
+ ppsa = &port->actions_list;
+ while (*ppsa) {
+ if ((*ppsa)->id == id) {
+ psa = *ppsa;
+ break;
+ }
+ ppsa = &(*ppsa)->next;
+ }
+ if (!psa)
+ printf("Failed to find shared action #%u on port %u\n",
+ id, port_id);
+ return psa;
+}
+
+static int
+action_alloc(portid_t port_id, uint32_t id,
+ struct port_shared_action **action)
+{
+ struct rte_port *port;
+ struct port_shared_action **ppsa;
+ struct port_shared_action *psa = NULL;
+
+ *action = NULL;
+ if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+ port_id == (portid_t)RTE_PORT_ALL)
+ return -EINVAL;
+ port = &ports[port_id];
+ if (id == UINT32_MAX) {
+ /* taking first available ID */
+ if (port->actions_list) {
+ if (port->actions_list->id == UINT32_MAX - 1) {
+ printf("Highest shared action ID is already"
+ " assigned, delete it first\n");
+ return -ENOMEM;
+ }
+ id = port->actions_list->id + 1;
+ } else {
+ id = 0;
+ }
+ }
+ psa = calloc(1, sizeof(*psa));
+ if (!psa) {
+ printf("Allocation of port %u shared action failed\n",
+ port_id);
+ return -ENOMEM;
+ }
+ ppsa = &port->actions_list;
+ while (*ppsa && (*ppsa)->id > id)
+ ppsa = &(*ppsa)->next;
+ if (*ppsa && (*ppsa)->id == id) {
+ printf("Shared action #%u is already assigned,"
+ " delete it first\n", id);
+ free(psa);
+ return -EINVAL;
+ }
+ psa->next = *ppsa;
+ psa->id = id;
+ *ppsa = psa;
+ *action = psa;
+ return 0;
+}
+
+/** Create shared action */
+int
+port_shared_action_create(portid_t port_id, uint32_t id,
+ const struct rte_flow_shared_action_conf *conf,
+ const struct rte_flow_action *action)
+{
+ struct port_shared_action *psa;
+ int ret;
+ struct rte_flow_error error;
+
+ ret = action_alloc(port_id, id, &psa);
+ if (ret)
+ return ret;
+ /* Poisoning to make sure PMDs update it in case of error. */
+ memset(&error, 0x22, sizeof(error));
+ psa->action = rte_flow_shared_action_create(port_id, conf, action,
+ &error);
+ if (!psa->action) {
+ uint32_t destroy_id = psa->id;
+ port_shared_action_destroy(port_id, 1, &destroy_id);
+ return port_flow_complain(&error);
+ }
+ psa->type = action->type;
+ printf("Shared action #%u created\n", psa->id);
+ return 0;
+}
+
+/** Destroy shared action */
+int
+port_shared_action_destroy(portid_t port_id,
+ uint32_t n,
+ const uint32_t *actions)
+{
+ struct rte_port *port;
+ struct port_shared_action **tmp;
+ uint32_t c = 0;
+ int ret = 0;
+
+ if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+ port_id == (portid_t)RTE_PORT_ALL)
+ return -EINVAL;
+ port = &ports[port_id];
+ tmp = &port->actions_list;
+ while (*tmp) {
+ uint32_t i;
+
+ for (i = 0; i != n; ++i) {
+ struct rte_flow_error error;
+ struct port_shared_action *psa = *tmp;
+
+ if (actions[i] != psa->id)
+ continue;
+ /*
+ * Poisoning to make sure PMDs update it in case
+ * of error.
+ */
+ memset(&error, 0x33, sizeof(error));
+
+ if (psa->action && rte_flow_shared_action_destroy(
+ port_id, psa->action, &error)) {
+ ret = port_flow_complain(&error);
+ continue;
+ }
+ *tmp = psa->next;
+ free(psa);
+ printf("Shared action #%u destroyed\n", psa->id);
+ break;
+ }
+ if (i == n)
+ tmp = &(*tmp)->next;
+ ++c;
+ }
+ return ret;
+}
+
+
+/** Get shared action by port + id */
+struct rte_flow_shared_action *
+port_shared_action_get_by_id(portid_t port_id, uint32_t id)
+{
+
+ struct port_shared_action *psa = action_get_by_id(port_id, id);
+
+ return (psa) ? psa->action : NULL;
+}
+
+/** Update shared action */
+int
+port_shared_action_update(portid_t port_id, uint32_t id,
+ const struct rte_flow_action *action)
+{
+ struct rte_flow_error error;
+ struct rte_flow_shared_action *shared_action;
+
+ shared_action = port_shared_action_get_by_id(port_id, id);
+ if (!shared_action)
+ return -EINVAL;
+ if (rte_flow_shared_action_update(port_id, shared_action, action,
+ &error)) {
+ return port_flow_complain(&error);
+ }
+ printf("Shared action #%u updated\n", id);
+ return 0;
+}
+
+int
+port_shared_action_query(portid_t port_id, uint32_t id)
+{
+ struct rte_flow_error error;
+ struct port_shared_action *psa;
+ uint64_t default_data;
+ void *data = NULL;
+ int ret = 0;
+
+ psa = action_get_by_id(port_id, id);
+ if (!psa)
+ return -EINVAL;
+ switch (psa->type) {
+ case RTE_FLOW_ACTION_TYPE_RSS:
+ data = &default_data;
+ break;
+ default:
+ printf("Shared action %u (type: %d) on port %u doesn't support"
+ " query\n", id, psa->type, port_id);
+ return -1;
+ }
+ if (rte_flow_shared_action_query(port_id, psa->action, data, &error))
+ ret = port_flow_complain(&error);
+ switch (psa->type) {
+ case RTE_FLOW_ACTION_TYPE_RSS:
+ if (!ret)
+ printf("Shared RSS action:\n\trefs:%u\n",
+ *((uint32_t *)data));
+ data = NULL;
+ break;
+ default:
+ printf("Shared action %u (type: %d) on port %u doesn't support"
+ " query\n", id, psa->type, port_id);
+ ret = -1;
+ }
+ return ret;
+}
+
/** Validate flow rule. */
int
port_flow_validate(portid_t port_id,
uint8_t data[]; /**< Storage for flow rule description */
};
+/* Descriptor for shared action */
+struct port_shared_action {
+ struct port_shared_action *next; /**< Next flow in list. */
+ uint32_t id; /**< Shared action ID. */
+ enum rte_flow_action_type type; /**< Action type. */
+ struct rte_flow_shared_action *action; /**< Shared action handle. */
+};
+
/**
* The data structure associated with each port.
*/
uint32_t mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
uint8_t slave_flag; /**< bonding slave port */
struct port_flow *flow_list; /**< Associated flows. */
+ struct port_shared_action *actions_list;
+ /**< Associated shared actions. */
const struct rte_eth_rxtx_callback *rx_dump_cb[RTE_MAX_QUEUES_PER_PORT+1];
const struct rte_eth_rxtx_callback *tx_dump_cb[RTE_MAX_QUEUES_PER_PORT+1];
/**< metadata value to insert in Tx packets. */
uint8_t bit1_pos, uint8_t bit2_pos, uint32_t value);
void port_reg_display(portid_t port_id, uint32_t reg_off);
void port_reg_set(portid_t port_id, uint32_t reg_off, uint32_t value);
+int port_shared_action_create(portid_t port_id, uint32_t id,
+ const struct rte_flow_shared_action_conf *conf,
+ const struct rte_flow_action *action);
+int port_shared_action_destroy(portid_t port_id,
+ uint32_t n, const uint32_t *action);
+struct rte_flow_shared_action *port_shared_action_get_by_id(portid_t port_id,
+ uint32_t id);
+int port_shared_action_update(portid_t port_id, uint32_t id,
+ const struct rte_flow_action *action);
int port_flow_validate(portid_t port_id,
const struct rte_flow_attr *attr,
const struct rte_flow_item *pattern,
const struct rte_flow_attr *attr,
const struct rte_flow_item *pattern,
const struct rte_flow_action *actions);
+int port_shared_action_query(portid_t port_id, uint32_t id);
void update_age_action_context(const struct rte_flow_action *actions,
struct port_flow *pf);
int port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule);
- ``dscp_value {unsigned}``: The new DSCP value to be set
+- ``shared``: Use shared action created via
+ ``flow shared_action {port_id} create``
+
+ - ``shared_action_id {unsigned}``: Shared action ID to use
+
Destroying flow rules
~~~~~~~~~~~~~~~~~~~~~
testpmd> flow aged 0
Port 0 total aged flows: 0
+Creating shared actions
+~~~~~~~~~~~~~~~~~~~~~~~
+``flow shared_action {port_id} create`` creates shared action with optional
+shared action ID. It is bound to ``rte_flow_shared_action_create()``::
+
+ flow shared_action {port_id} create [action_id {shared_action_id}]
+ [ingress] [egress] action {action} / end
+
+If successful, it will show::
+
+ Shared action #[...] created
+
+Otherwise, it will complain either that shared action already exists or that
+some error occurred::
+
+ Shared action #[...] is already assigned, delete it first
+
+::
+
+ Caught error type [...] ([...]): [...]
+
+Create shared rss action with id 100 to queues 1 and 2 on port 0::
+
+ testpmd> flow shared_action 0 create action_id 100 \
+ ingress action rss queues 1 2 end / end
+
+Create shared rss action with id assigned by testpmd to queues 1 and 2 on
+port 0::
+
+ testpmd> flow shared_action 0 create action_id \
+ ingress action rss queues 0 1 end / end
+
+Updating shared actions
+~~~~~~~~~~~~~~~~~~~~~~~
+``flow shared_action {port_id} update`` updates configuration of the shared
+action from its shared action ID (as returned by
+``flow shared_action {port_id} create``). It is bound to
+``rte_flow_shared_action_update()``::
+
+ flow shared_action {port_id} update {shared_action_id}
+ action {action} / end
+
+If successful, it will show::
+
+ Shared action #[...] updated
+
+Otherwise, it will complain either that shared action not found or that some
+error occurred::
+
+ Failed to find shared action #[...] on port [...]
+
+::
+
+ Caught error type [...] ([...]): [...]
+
+Update shared rss action having id 100 on port 0 with rss to queues 0 and 3
+(in create example above rss queues were 1 and 2)::
+
+ testpmd> flow shared_action 0 update 100 action rss queues 0 3 end / end
+
+Destroying shared actions
+~~~~~~~~~~~~~~~~~~~~~~~~~
+``flow shared_action {port_id} update`` destroys one or more shared actions
+from their shared action IDs (as returned by
+``flow shared_action {port_id} create``). It is bound to
+``rte_flow_shared_action_destroy()``::
+
+ flow shared_action {port_id} destroy action_id {shared_action_id} [...]
+
+If successful, it will show::
+
+ Shared action #[...] destroyed
+
+It does not report anything for shared action IDs that do not exist.
+The usual error message is shown when a shared action cannot be destroyed::
+
+ Caught error type [...] ([...]): [...]
+
+Destroy shared actions having id 100 & 101::
+
+ testpmd> flow shared_action 0 destroy action_id 100 action_id 101
+
+Query shared actions
+~~~~~~~~~~~~~~~~~~~~
+``flow shared_action {port_id} query`` queries the shared action from its
+shared action ID (as returned by ``flow shared_action {port_id} create``).
+It is bound to ``rte_flow_shared_action_query()``::
+
+ flow shared_action {port_id} query {shared_action_id}
+
+Currently only rss shared action supported. If successful, it will show::
+
+ Shared RSS action:
+ refs:[...]
+
+Otherwise, it will complain either that shared action not found or that some
+error occurred::
+
+ Failed to find shared action #[...] on port [...]
+
+::
+
+ Caught error type [...] ([...]): [...]
+
+Query shared action having id 100::
+
+ testpmd> flow shared_action 0 query 100
Sample QinQ flow rules
~~~~~~~~~~~~~~~~~~~~~~