diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 88108498e0..34d28936b9 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -149,6 +149,7 @@ enum index {
 	QUEUE_INDIRECT_ACTION_UPDATE,
 	QUEUE_INDIRECT_ACTION_DESTROY,
 	QUEUE_INDIRECT_ACTION_QUERY,
+	QUEUE_INDIRECT_ACTION_QUERY_UPDATE,
 
 	/* Queue indirect action create arguments */
 	QUEUE_INDIRECT_ACTION_CREATE_ID,
@@ -168,6 +169,9 @@ enum index {
 	/* Queue indirect action query arguments */
 	QUEUE_INDIRECT_ACTION_QUERY_POSTPONE,
 
+	/* Queue indirect action query_update arguments */
+	QUEUE_INDIRECT_ACTION_QU_MODE,
+
 	/* Push arguments. */
 	PUSH_QUEUE,
 
@@ -227,6 +231,7 @@ enum index {
 	CONFIG_AGING_OBJECTS_NUMBER,
 	CONFIG_METERS_NUMBER,
 	CONFIG_CONN_TRACK_NUMBER,
+	CONFIG_QUOTAS_NUMBER,
 	CONFIG_FLAGS,
 
 	/* Indirect action arguments */
@@ -234,6 +239,7 @@ enum index {
 	INDIRECT_ACTION_UPDATE,
 	INDIRECT_ACTION_DESTROY,
 	INDIRECT_ACTION_QUERY,
+	INDIRECT_ACTION_QUERY_UPDATE,
 
 	/* Indirect action create arguments */
 	INDIRECT_ACTION_CREATE_ID,
@@ -245,6 +251,10 @@ enum index {
 	/* Indirect action destroy arguments */
 	INDIRECT_ACTION_DESTROY_ID,
 
+	/* Indirect action query-and-update arguments */
+	INDIRECT_ACTION_QU_MODE,
+	INDIRECT_ACTION_QU_MODE_NAME,
+
 	/* Validate/create pattern. */
 	ITEM_PATTERN,
 	ITEM_PARAM_IS,
@@ -465,6 +475,9 @@ enum index {
 	ITEM_METER,
 	ITEM_METER_COLOR,
 	ITEM_METER_COLOR_NAME,
+	ITEM_QUOTA,
+	ITEM_QUOTA_STATE,
+	ITEM_QUOTA_STATE_NAME,
 
 	/* Validate/create actions. */
 	ACTIONS,
@@ -621,6 +634,14 @@ enum index {
 	ACTION_REPRESENTED_PORT,
 	ACTION_REPRESENTED_PORT_ETHDEV_PORT_ID,
 	ACTION_SEND_TO_KERNEL,
+	ACTION_QUOTA_CREATE,
+	ACTION_QUOTA_CREATE_LIMIT,
+	ACTION_QUOTA_CREATE_MODE,
+	ACTION_QUOTA_CREATE_MODE_NAME,
+	ACTION_QUOTA_QU,
+	ACTION_QUOTA_QU_LIMIT,
+	ACTION_QUOTA_QU_UPDATE_OP,
+	ACTION_QUOTA_QU_UPDATE_OP_NAME,
 };
 
 /** Maximum size for pattern in struct rte_flow_item_raw. */
@@ -1011,6 +1032,7 @@ struct buffer {
 		} ia_destroy; /**< Indirect action destroy arguments. */
 		struct {
 			uint32_t action_id;
+			enum rte_flow_query_update_mode qu_mode;
 		} ia; /* Indirect action query arguments */
 		struct {
 			uint32_t table_id;
@@ -1097,6 +1119,7 @@ static const enum index next_config_attr[] = {
 	CONFIG_AGING_OBJECTS_NUMBER,
 	CONFIG_METERS_NUMBER,
 	CONFIG_CONN_TRACK_NUMBER,
+	CONFIG_QUOTAS_NUMBER,
 	CONFIG_FLAGS,
 	END,
 	ZERO,
@@ -1190,6 +1213,7 @@ static const enum index next_qia_subcmd[] = {
 	QUEUE_INDIRECT_ACTION_UPDATE,
 	QUEUE_INDIRECT_ACTION_DESTROY,
 	QUEUE_INDIRECT_ACTION_QUERY,
+	QUEUE_INDIRECT_ACTION_QUERY_UPDATE,
 	ZERO,
 };
 
@@ -1231,6 +1255,25 @@ static const enum index next_ia_create_attr[] = {
 	ZERO,
 };
 
+static const enum index next_ia[] = {
+	INDIRECT_ACTION_ID2PTR,
+	ACTION_NEXT,
+	ZERO
+};
+
+static const enum index next_qia_qu_attr[] = {
+	QUEUE_INDIRECT_ACTION_QU_MODE,
+	QUEUE_INDIRECT_ACTION_UPDATE_POSTPONE,
+	INDIRECT_ACTION_SPEC,
+	ZERO
+};
+
+static const enum index next_ia_qu_attr[] = {
+	INDIRECT_ACTION_QU_MODE,
+	INDIRECT_ACTION_SPEC,
+	ZERO
+};
+
 static const enum index next_dump_subcmd[] = {
 	DUMP_ALL,
 	DUMP_ONE,
@@ -1242,6 +1285,7 @@ static const enum index next_ia_subcmd[] = {
 	INDIRECT_ACTION_UPDATE,
 	INDIRECT_ACTION_DESTROY,
 	INDIRECT_ACTION_QUERY,
+	INDIRECT_ACTION_QUERY_UPDATE,
 	ZERO,
 };
 
@@ -1355,6 +1399,7 @@ static const enum index next_item[] = {
 	ITEM_L2TPV2,
 	ITEM_PPP,
 	ITEM_METER,
+	ITEM_QUOTA,
 	END_SET,
 	ZERO,
 };
@@ -1821,6 +1866,12 @@ static const enum index item_meter[] = {
 	ZERO,
 };
 
+static const enum index item_quota[] = {
+	ITEM_QUOTA_STATE,
+	ITEM_NEXT,
+	ZERO,
+};
+
 static const enum index next_action[] = {
 	ACTION_END,
 	ACTION_VOID,
@@ -1886,9 +1937,25 @@ static const enum index next_action[] = {
 	ACTION_PORT_REPRESENTOR,
 	ACTION_REPRESENTED_PORT,
 	ACTION_SEND_TO_KERNEL,
+	ACTION_QUOTA_CREATE,
+	ACTION_QUOTA_QU,
 	ZERO,
 };
 
+static const enum index action_quota_create[] = {
+	ACTION_QUOTA_CREATE_LIMIT,
+	ACTION_QUOTA_CREATE_MODE,
+	ACTION_NEXT,
+	ZERO
+};
+
+static const enum index action_quota_update[] = {
+	ACTION_QUOTA_QU_LIMIT,
+	ACTION_QUOTA_QU_UPDATE_OP,
+	ACTION_NEXT,
+	ZERO
+};
+
 static const enum index action_mark[] = {
 	ACTION_MARK_ID,
 	ACTION_NEXT,
@@ -2399,6 +2466,22 @@ static int parse_meter_policy_id2ptr(struct context *ctx,
 static int parse_meter_color(struct context *ctx, const struct token *token,
 			     const char *str, unsigned int len, void *buf,
 			     unsigned int size);
+static int
+parse_quota_state_name(struct context *ctx, const struct token *token,
+		       const char *str, unsigned int len, void *buf,
+		       unsigned int size);
+static int
+parse_quota_mode_name(struct context *ctx, const struct token *token,
+		      const char *str, unsigned int len, void *buf,
+		      unsigned int size);
+static int
+parse_quota_update_name(struct context *ctx, const struct token *token,
+			const char *str, unsigned int len, void *buf,
+			unsigned int size);
+static int
+parse_qu_mode_name(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 *,
@@ -2431,6 +2514,18 @@ static int comp_queue_id(struct context *, const struct token *,
 			 unsigned int, char *, unsigned int);
 static int comp_meter_color(struct context *, const struct token *,
 			    unsigned int, char *, unsigned int);
+static int
+comp_quota_state_name(struct context *ctx, const struct token *token,
+		      unsigned int ent, char *buf, unsigned int size);
+static int
+comp_quota_mode_name(struct context *ctx, const struct token *token,
+		     unsigned int ent, char *buf, unsigned int size);
+static int
+comp_quota_update_name(struct context *ctx, const struct token *token,
+		       unsigned int ent, char *buf, unsigned int size);
+static int
+comp_qu_mode_name(struct context *ctx, const struct token *token,
+		  unsigned int ent, char *buf, unsigned int size);
 
 /** Token definitions. */
 static const struct token token_list[] = {
@@ -2695,6 +2790,14 @@ static const struct token token_list[] = {
 		.args = ARGS(ARGS_ENTRY(struct buffer,
 					args.configure.port_attr.nb_aging_objects)),
 	},
+	[CONFIG_QUOTAS_NUMBER] = {
+		.name = "quotas_number",
+		.help = "number of quotas",
+		.next = NEXT(next_config_attr,
+			     NEXT_ENTRY(COMMON_UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct buffer,
+				     args.configure.port_attr.nb_quotas)),
+	},
 	[CONFIG_METERS_NUMBER] = {
 		.name = "meters_number",
 		.help = "number of meters",
@@ -3077,7 +3180,7 @@ static const struct token token_list[] = {
 		.help = "query indirect action",
 		.next = NEXT(next_qia_query_attr,
 			     NEXT_ENTRY(COMMON_INDIRECT_ACTION_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, args.vc.attr.group)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.action_id)),
 		.call = parse_qia,
 	},
 	/* Indirect action destroy arguments. */
@@ -3097,6 +3200,21 @@ static const struct token token_list[] = {
 					    args.ia_destroy.action_id)),
 		.call = parse_qia_destroy,
 	},
+	[QUEUE_INDIRECT_ACTION_QUERY_UPDATE] = {
+		.name = "query_update",
+		.help = "indirect query [and|or] update action",
+		.next = NEXT(next_qia_qu_attr, NEXT_ENTRY(COMMON_INDIRECT_ACTION_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.action_id)),
+		.call = parse_qia
+	},
+	[QUEUE_INDIRECT_ACTION_QU_MODE] = {
+		.name = "mode",
+		.help = "indirect query [and|or] update action",
+		.next = NEXT(next_qia_qu_attr,
+			     NEXT_ENTRY(INDIRECT_ACTION_QU_MODE_NAME)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.qu_mode)),
+		.call = parse_qia
+	},
 	/* Indirect action update arguments. */
 	[QUEUE_INDIRECT_ACTION_UPDATE_POSTPONE] = {
 		.name = "postpone",
@@ -3220,6 +3338,27 @@ static const struct token token_list[] = {
 		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.action_id)),
 		.call = parse_ia,
 	},
+	[INDIRECT_ACTION_QUERY_UPDATE] = {
+		.name = "query_update",
+		.help = "query [and|or] update",
+		.next = NEXT(next_ia_qu_attr, NEXT_ENTRY(COMMON_INDIRECT_ACTION_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.action_id)),
+		.call = parse_ia
+	},
+	[INDIRECT_ACTION_QU_MODE] = {
+		.name = "mode",
+		.help = "query_update mode",
+		.next = NEXT(next_ia_qu_attr,
+			     NEXT_ENTRY(INDIRECT_ACTION_QU_MODE_NAME)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.ia.qu_mode)),
+		.call = parse_ia,
+	},
+	[INDIRECT_ACTION_QU_MODE_NAME] = {
+		.name = "mode_name",
+		.help = "query-update mode name",
+		.call = parse_qu_mode_name,
+		.comp = comp_qu_mode_name,
+	},
 	[VALIDATE] = {
 		.name = "validate",
 		.help = "check whether a flow rule can be created",
@@ -5127,6 +5266,26 @@ static const struct token token_list[] = {
 		.call = parse_meter_color,
 		.comp = comp_meter_color,
 	},
+	[ITEM_QUOTA] = {
+		.name = "quota",
+		.help = "match quota",
+		.priv = PRIV_ITEM(QUOTA, sizeof(struct rte_flow_item_quota)),
+		.next = NEXT(item_quota),
+		.call = parse_vc
+	},
+	[ITEM_QUOTA_STATE] = {
+		.name = "quota_state",
+		.help = "quota state",
+		.next = NEXT(item_quota, NEXT_ENTRY(ITEM_QUOTA_STATE_NAME),
+			     NEXT_ENTRY(ITEM_PARAM_SPEC, ITEM_PARAM_MASK)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_quota, state))
+	},
+	[ITEM_QUOTA_STATE_NAME] = {
+		.name = "state_name",
+		.help = "quota state name",
+		.call = parse_quota_state_name,
+		.comp = comp_quota_state_name
+	},
 	/* Validate/create actions. */
 	[ACTIONS] = {
 		.name = "actions",
@@ -6360,7 +6519,7 @@ static const struct token token_list[] = {
 		.name = "indirect",
 		.help = "apply indirect action by id",
 		.priv = PRIV_ACTION(INDIRECT, 0),
-		.next = NEXT(NEXT_ENTRY(INDIRECT_ACTION_ID2PTR)),
+		.next = NEXT(next_ia),
 		.args = ARGS(ARGS_ENTRY_ARB(0, sizeof(uint32_t))),
 		.call = parse_vc,
 	},
@@ -6411,6 +6570,64 @@ static const struct token token_list[] = {
 		.help = "submit a list of associated actions for red",
 		.next = NEXT(next_action),
 	},
+	[ACTION_QUOTA_CREATE] = {
+		.name = "quota_create",
+		.help = "create quota action",
+		.priv = PRIV_ACTION(QUOTA,
+				    sizeof(struct rte_flow_action_quota)),
+		.next = NEXT(action_quota_create),
+		.call = parse_vc
+	},
+	[ACTION_QUOTA_CREATE_LIMIT] = {
+		.name = "limit",
+		.help = "quota limit",
+		.next = NEXT(action_quota_create, NEXT_ENTRY(COMMON_UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_quota, quota)),
+		.call = parse_vc_conf
+	},
+	[ACTION_QUOTA_CREATE_MODE] = {
+		.name = "mode",
+		.help = "quota mode",
+		.next = NEXT(action_quota_create,
+			     NEXT_ENTRY(ACTION_QUOTA_CREATE_MODE_NAME)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_quota, mode)),
+		.call = parse_vc_conf
+	},
+	[ACTION_QUOTA_CREATE_MODE_NAME] = {
+		.name = "mode_name",
+		.help = "quota mode name",
+		.call = parse_quota_mode_name,
+		.comp = comp_quota_mode_name
+	},
+	[ACTION_QUOTA_QU] = {
+		.name = "quota_update",
+		.help = "update quota action",
+		.priv = PRIV_ACTION(QUOTA,
+				    sizeof(struct rte_flow_update_quota)),
+		.next = NEXT(action_quota_update),
+		.call = parse_vc
+	},
+	[ACTION_QUOTA_QU_LIMIT] = {
+		.name = "limit",
+		.help = "quota limit",
+		.next = NEXT(action_quota_update, NEXT_ENTRY(COMMON_UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_update_quota, quota)),
+		.call = parse_vc_conf
+	},
+	[ACTION_QUOTA_QU_UPDATE_OP] = {
+		.name = "update_op",
+		.help = "query update op SET|ADD",
+		.next = NEXT(action_quota_update,
+			     NEXT_ENTRY(ACTION_QUOTA_QU_UPDATE_OP_NAME)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_update_quota, op)),
+		.call = parse_vc_conf
+	},
+	[ACTION_QUOTA_QU_UPDATE_OP_NAME] = {
+		.name = "update_op_name",
+		.help = "quota update op name",
+		.call = parse_quota_update_name,
+		.comp = comp_quota_update_name
+	},
 
 	/* Top-level command. */
 	[ADD] = {
@@ -6656,6 +6873,7 @@ parse_ia(struct context *ctx, const struct token *token,
 	switch (ctx->curr) {
 	case INDIRECT_ACTION_CREATE:
 	case INDIRECT_ACTION_UPDATE:
+	case INDIRECT_ACTION_QUERY_UPDATE:
 		out->args.vc.actions =
 			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
 					       sizeof(double));
@@ -6676,6 +6894,8 @@ parse_ia(struct context *ctx, const struct token *token,
 	case INDIRECT_ACTION_TRANSFER:
 		out->args.vc.attr.transfer = 1;
 		return len;
+	case INDIRECT_ACTION_QU_MODE:
+		return len;
 	default:
 		return -1;
 	}
@@ -6748,6 +6968,7 @@ parse_qia(struct context *ctx, const struct token *token,
 		return len;
 	case QUEUE_INDIRECT_ACTION_CREATE:
 	case QUEUE_INDIRECT_ACTION_UPDATE:
+	case QUEUE_INDIRECT_ACTION_QUERY_UPDATE:
 		out->args.vc.actions =
 			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
 					       sizeof(double));
@@ -6770,6 +6991,8 @@ parse_qia(struct context *ctx, const struct token *token,
 		return len;
 	case QUEUE_INDIRECT_ACTION_CREATE_POSTPONE:
 		return len;
+	case QUEUE_INDIRECT_ACTION_QU_MODE:
+		return len;
 	default:
 		return -1;
 	}
@@ -10052,6 +10275,107 @@ parse_meter_color(struct context *ctx, const struct token *token,
 	return len;
 }
 
+static int
+parse_name_to_index(struct context *ctx, const struct token *token,
+		    const char *str, unsigned int len, void *buf,
+		    unsigned int size,
+		    const char *const names[], size_t names_size, uint32_t *dst)
+{
+	int ret;
+	uint32_t i;
+
+	RTE_SET_USED(token);
+	RTE_SET_USED(buf);
+	RTE_SET_USED(size);
+	if (!ctx->object)
+		return len;
+	for (i = 0; i < names_size; i++) {
+		if (!names[i])
+			continue;
+		ret = strcmp_partial(names[i], str,
+				     RTE_MIN(len, strlen(names[i])));
+		if (!ret) {
+			*dst = i;
+			return len;
+		}
+	}
+	return -1;
+}
+
+static const char *const quota_mode_names[] = {
+	NULL,
+	[RTE_FLOW_QUOTA_MODE_PACKET] = "packet",
+	[RTE_FLOW_QUOTA_MODE_L2] = "l2",
+	[RTE_FLOW_QUOTA_MODE_L3] = "l3"
+};
+
+static const char *const quota_state_names[] = {
+	[RTE_FLOW_QUOTA_STATE_PASS] = "pass",
+	[RTE_FLOW_QUOTA_STATE_BLOCK] = "block"
+};
+
+static const char *const quota_update_names[] = {
+	[RTE_FLOW_UPDATE_QUOTA_SET] = "set",
+	[RTE_FLOW_UPDATE_QUOTA_ADD] = "add"
+};
+
+static const char *const query_update_mode_names[] = {
+	[RTE_FLOW_QU_QUERY_FIRST] = "query_first",
+	[RTE_FLOW_QU_UPDATE_FIRST] = "update_first"
+};
+
+static int
+parse_quota_state_name(struct context *ctx, const struct token *token,
+		       const char *str, unsigned int len, void *buf,
+		       unsigned int size)
+{
+	struct rte_flow_item_quota *quota = ctx->object;
+
+	return parse_name_to_index(ctx, token, str, len, buf, size,
+				   quota_state_names,
+				   RTE_DIM(quota_state_names),
+				   (uint32_t *)&quota->state);
+}
+
+static int
+parse_quota_mode_name(struct context *ctx, const struct token *token,
+		      const char *str, unsigned int len, void *buf,
+		      unsigned int size)
+{
+	struct rte_flow_action_quota *quota = ctx->object;
+
+	return parse_name_to_index(ctx, token, str, len, buf, size,
+				   quota_mode_names,
+				   RTE_DIM(quota_mode_names),
+				   (uint32_t *)&quota->mode);
+}
+
+static int
+parse_quota_update_name(struct context *ctx, const struct token *token,
+			const char *str, unsigned int len, void *buf,
+			unsigned int size)
+{
+	struct rte_flow_update_quota *update = ctx->object;
+
+	return parse_name_to_index(ctx, token, str, len, buf, size,
+				   quota_update_names,
+				   RTE_DIM(quota_update_names),
+				   (uint32_t *)&update->op);
+}
+
+static int
+parse_qu_mode_name(struct context *ctx, const struct token *token,
+		   const char *str, unsigned int len, void *buf,
+		   unsigned int size)
+{
+	struct buffer *out = ctx->object;
+
+	return parse_name_to_index(ctx, token, str, len, buf, size,
+				   query_update_mode_names,
+				   RTE_DIM(query_update_mode_names),
+				   (uint32_t *)&out->args.ia.qu_mode);
+}
+
 /** No completion. */
 static int
 comp_none(struct context *ctx, const struct token *token,
@@ -10343,6 +10667,21 @@ comp_queue_id(struct context *ctx, const struct token *token,
 	return i;
 }
 
+static int
+comp_names_to_index(struct context *ctx, const struct token *token,
+		    unsigned int ent, char *buf, unsigned int size,
+		    const char *const names[], size_t names_size)
+{
+	RTE_SET_USED(ctx);
+	RTE_SET_USED(token);
+	if (!buf)
+		return names_size;
+	if (names[ent] && ent < names_size)
+		return rte_strscpy(buf, names[ent], size);
+	return -1;
+
+}
+
 /** Complete available Meter colors. */
 static int
 comp_meter_color(struct context *ctx, const struct token *token,
@@ -10357,6 +10696,42 @@ comp_meter_color(struct context *ctx, const struct token *token,
 	return -1;
 }
 
+static int
+comp_quota_state_name(struct context *ctx, const struct token *token,
+		      unsigned int ent, char *buf, unsigned int size)
+{
+	return comp_names_to_index(ctx, token, ent, buf, size,
+				   quota_state_names,
+				   RTE_DIM(quota_state_names));
+}
+
+static int
+comp_quota_mode_name(struct context *ctx, const struct token *token,
+		     unsigned int ent, char *buf, unsigned int size)
+{
+	return comp_names_to_index(ctx, token, ent, buf, size,
+				   quota_mode_names,
+				   RTE_DIM(quota_mode_names));
+}
+
+static int
+comp_quota_update_name(struct context *ctx, const struct token *token,
+		       unsigned int ent, char *buf, unsigned int size)
+{
+	return comp_names_to_index(ctx, token, ent, buf, size,
+				   quota_update_names,
+				   RTE_DIM(quota_update_names));
+}
+
+static int
+comp_qu_mode_name(struct context *ctx, const struct token *token,
+		  unsigned int ent, char *buf, unsigned int size)
+{
+	return comp_names_to_index(ctx, token, ent, buf, size,
+				   query_update_mode_names,
+				   RTE_DIM(query_update_mode_names));
+}
+
 /** Internal context. */
 static struct context cmd_flow_context;
 
@@ -10715,7 +11090,14 @@ cmd_flow_parsed(const struct buffer *in)
 	case QUEUE_INDIRECT_ACTION_QUERY:
 		port_queue_action_handle_query(in->port,
 					       in->queue, in->postpone,
-					       in->args.vc.attr.group);
+					       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);
 		break;
 	case INDIRECT_ACTION_CREATE:
 		port_action_handle_create(
@@ -10739,6 +11121,12 @@ cmd_flow_parsed(const struct buffer *in)
 	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,
+						in->args.ia.action_id,
+						in->args.ia.qu_mode,
+						in->args.vc.actions);
+		break;
 	case VALIDATE:
 		port_flow_validate(in->port, &in->args.vc.attr,
 				   in->args.vc.pattern, in->args.vc.actions,
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index acccb6b035..820ede5501 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -1901,9 +1901,13 @@ port_action_handle_update(portid_t port_id, uint32_t id,
 }
 
 static void
-port_action_handle_query_dump(uint32_t type, union port_action_query *query)
+port_action_handle_query_dump(portid_t port_id,
+			      const struct port_indirect_action *pia,
+			      union port_action_query *query)
 {
-	switch (type) {
+	if (!pia || !query)
+		return;
+	switch (pia->type) {
 	case RTE_FLOW_ACTION_TYPE_AGE:
 		printf("Indirect AGE action:\n"
 		       " aged: %u\n"
@@ -1967,15 +1971,41 @@ port_action_handle_query_dump(uint32_t type, union port_action_query *query)
 		       query->ct.reply_dir.max_win,
 		       query->ct.reply_dir.max_ack);
 		break;
+	case RTE_FLOW_ACTION_TYPE_QUOTA:
+		printf("Indirect QUOTA action %u\n"
+		       " unused quota: %" PRId64 "\n",
+		       pia->id, query->quota.quota);
+		break;
 	default:
-		fprintf(stderr,
-			"Indirect action (type: %d) doesn't support query\n",
-			type);
+		printf("port-%u: indirect action %u (type: %d) doesn't support query\n",
+		       pia->type, pia->id, port_id);
 		break;
 	}
 
 }
 
+void
+port_action_handle_query_update(portid_t port_id, uint32_t id,
+				enum rte_flow_query_update_mode qu_mode,
+				const struct rte_flow_action *action)
+{
+	int ret;
+	struct rte_flow_error error;
+	struct port_indirect_action *pia;
+	union port_action_query query;
+
+	pia = action_get_by_id(port_id, id);
+	if (!pia || !pia->handle)
+		return;
+	ret = rte_flow_action_handle_query_update(port_id, pia->handle, action,
+						  &query, qu_mode, &error);
+	if (ret)
+		port_flow_complain(&error);
+	else
+		port_action_handle_query_dump(port_id, pia, &query);
+
+}
+
 int
 port_action_handle_query(portid_t port_id, uint32_t id)
 {
@@ -1989,6 +2019,7 @@ port_action_handle_query(portid_t port_id, uint32_t id)
 	switch (pia->type) {
 	case RTE_FLOW_ACTION_TYPE_AGE:
 	case RTE_FLOW_ACTION_TYPE_COUNT:
+	case RTE_FLOW_ACTION_TYPE_QUOTA:
 		break;
 	default:
 		fprintf(stderr,
@@ -2001,7 +2032,7 @@ port_action_handle_query(portid_t port_id, uint32_t id)
 	memset(&query, 0, sizeof(query));
 	if (rte_flow_action_handle_query(port_id, pia->handle, &query, &error))
 		return port_flow_complain(&error);
-	port_action_handle_query_dump(pia->type, &query);
+	port_action_handle_query_dump(port_id, pia, &query);
 	return 0;
 }
 
@@ -2945,6 +2976,42 @@ port_queue_action_handle_update(portid_t port_id,
 	return 0;
 }
 
+void
+port_queue_action_handle_query_update(portid_t port_id,
+				      uint32_t queue_id, bool postpone,
+				      uint32_t id,
+				      enum rte_flow_query_update_mode qu_mode,
+				      const struct rte_flow_action *action)
+{
+	int ret;
+	struct rte_flow_error error;
+	struct port_indirect_action *pia = action_get_by_id(port_id, id);
+	const struct rte_flow_op_attr attr = { .postpone = postpone};
+	struct queue_job *job;
+
+	if (!pia || !pia->handle)
+		return;
+	job = calloc(1, sizeof(*job));
+	if (!job)
+		return;
+	job->type = QUEUE_JOB_TYPE_ACTION_QUERY;
+	job->pia = pia;
+
+	ret = rte_flow_async_action_handle_query_update(port_id, queue_id,
+							&attr, pia->handle,
+							action,
+							&job->query,
+							qu_mode, job,
+							&error);
+	if (ret) {
+		port_flow_complain(&error);
+		free(job);
+	} else {
+		printf("port-%u: indirect action #%u update-and-query queued\n",
+		       port_id, id);
+	}
+}
+
 /** Enqueue indirect action query operation. */
 int
 port_queue_action_handle_query(portid_t port_id,
@@ -3215,7 +3282,8 @@ port_queue_flow_pull(portid_t port_id, queueid_t queue_id)
 		else if (job->type == QUEUE_JOB_TYPE_ACTION_DESTROY)
 			free(job->pia);
 		else if (job->type == QUEUE_JOB_TYPE_ACTION_QUERY)
-			port_action_handle_query_dump(job->pia->type, &job->query);
+			port_action_handle_query_dump(port_id, job->pia,
+						      &job->query);
 		free(job);
 	}
 	printf("Queue #%u pulled %u operations (%u failed, %u succeeded)\n",
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 7d24d25970..babc94246f 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -236,6 +236,7 @@ union port_action_query {
 	struct rte_flow_query_count count;
 	struct rte_flow_query_age age;
 	struct rte_flow_action_conntrack ct;
+	struct rte_flow_query_quota quota;
 };
 
 /* Descriptor for queue job. */
@@ -904,6 +905,10 @@ struct rte_flow_action_handle *port_action_handle_get_by_id(portid_t port_id,
 							    uint32_t id);
 int port_action_handle_update(portid_t port_id, uint32_t id,
 			      const struct rte_flow_action *action);
+void
+port_action_handle_query_update(portid_t port_id, uint32_t id,
+				enum rte_flow_query_update_mode qu_mode,
+				const struct rte_flow_action *action);
 int port_flow_get_info(portid_t port_id);
 int port_flow_configure(portid_t port_id,
 			const struct rte_flow_port_attr *port_attr,
@@ -948,6 +953,12 @@ int port_queue_action_handle_update(portid_t port_id, uint32_t queue_id,
 				    const struct rte_flow_action *action);
 int port_queue_action_handle_query(portid_t port_id, uint32_t queue_id,
 				   bool postpone, uint32_t id);
+void
+port_queue_action_handle_query_update(portid_t port_id,
+				      uint32_t queue_id, bool postpone,
+				      uint32_t id,
+				      enum rte_flow_query_update_mode qu_mode,
+				      const struct rte_flow_action *action);
 int port_queue_flow_push(portid_t port_id, queueid_t queue_id);
 int port_queue_flow_pull(portid_t port_id, queueid_t queue_id);
 void port_queue_flow_aged(portid_t port_id, uint32_t queue_id, uint8_t destroy);
diff --git a/doc/guides/nics/features/default.ini b/doc/guides/nics/features/default.ini
index 510cc6679d..92d8765fd5 100644
--- a/doc/guides/nics/features/default.ini
+++ b/doc/guides/nics/features/default.ini
@@ -132,6 +132,7 @@ ppp                  =
 pppoed               =
 pppoes               =
 pppoe_proto_id       =
+quota                =
 raw                  =
 represented_port     =
 sctp                 =
@@ -178,6 +179,7 @@ pf                   =
 port_id              =
 port_representor     =
 queue                =
+quota                =
 raw_decap            =
 raw_encap            =
 represented_port     =
diff --git a/doc/guides/nics/features/mlx5.ini b/doc/guides/nics/features/mlx5.ini
index 62fd330e2b..ff9ea0cb43 100644
--- a/doc/guides/nics/features/mlx5.ini
+++ b/doc/guides/nics/features/mlx5.ini
@@ -80,6 +80,7 @@ mpls                 = Y
 nvgre                = Y
 port_id              = Y
 port_representor     = Y
+quota                = Y
 tag                  = Y
 tcp                  = Y
 udp                  = Y
@@ -112,6 +113,7 @@ of_set_vlan_pcp      = Y
 of_set_vlan_vid      = Y
 port_id              = Y
 queue                = Y
+quota                = Y
 raw_decap            = Y
 raw_encap            = Y
 represented_port     = Y
diff --git a/doc/guides/nics/mlx5.rst b/doc/guides/nics/mlx5.rst
index 51f51259e3..1aac8becf2 100644
--- a/doc/guides/nics/mlx5.rst
+++ b/doc/guides/nics/mlx5.rst
@@ -291,6 +291,18 @@ Limitations
 - No Tx metadata go to the E-Switch steering domain for the Flow group 0.
   The flows within group 0 and set metadata action are rejected by hardware.
 
+- Quota:
+
+  - Template API only (HWS).
+  - Quota flow action and item supported in non-root HWS tables – flow group must be > 0.
+  - Quota implemented as indirect flow action only.
+  - Maximal value for quota SET and ADD operations in INT32_MAX (2G)
+  - Application cannot use 2 consecutive ADD updates.
+    Next tokens update after ADD must always be SET.
+  - HW can reduce non-negative quota to negative value.
+  - Quota flow action cannot be used with Meter or CT flow actions in the same rule.
+  - Maximal number of HW quota and HW meter objects <= 16e6.
+
 .. note::
 
    MAC addresses not already present in the bridge table of the associated
diff --git a/doc/guides/prog_guide/rte_flow.rst b/doc/guides/prog_guide/rte_flow.rst
index 3e6242803d..7a3868638c 100644
--- a/doc/guides/prog_guide/rte_flow.rst
+++ b/doc/guides/prog_guide/rte_flow.rst
@@ -1544,6 +1544,13 @@ Matches Color Marker set by a Meter.
 
 - ``color``: Metering color marker.
 
+Item: ``QUOTA``
+^^^^^^^^^^^^^^^
+
+Matches flow quota state set by quota action.
+
+- ``state``: Flow quota state
+
 Actions
 ~~~~~~~
 
@@ -3227,6 +3234,40 @@ and rte_mtr_policy_get() API respectively.
    | ``policy``       | Meter policy object  |
    +------------------+----------------------+
 
+Action: ``QUOTA``
+^^^^^^^^^^^^^^^^^
+
+Update ``quota`` value and set packet quota state.
+If the ``quota`` value after update is non-negative packet quota state set to
+``RTE_FLOW_QUOTA_STATE_PASS``. Otherwise, packet quota state set to ``RTE_FLOW_QUOTA_STATE_BLOCK``.
+The ``quota`` value is reduced according to ``mode`` setting.
+
+.. _table_rte_flow_action_quota:
+
+.. table:: QUOTA
+
+   +------------------+------------------------+
+   | Field            | Value                  |
+   +==================+========================+
+   | ``mode``         | Quota operational mode |
+   +------------------+------------------------+
+   | ``quota``        | Quota value            |
+   +------------------+------------------------+
+
+.. _rte_flow_quota_mode:
+
+.. table:: Quota update modes
+
+   +---------------------------------+-------------------------------------+
+   | Value                           | Description                         |
+   +=================================+=====================================+
+   | ``RTE_FLOW_QUOTA_MODE_PACKET``  | Count packets                       |
+   +---------------------------------+-------------------------------------+
+   | ``RTE_FLOW_QUOTA_MODE_L2``      | Count packet bytes starting from L2 |
+   +------------------+----------------------------------------------------+
+   | ``RTE_FLOW_QUOTA_MODE_L3``      | Count packet bytes starting from L3 |
+   +------------------+----------------------------------------------------+
+
 Negative types
 ~~~~~~~~~~~~~~
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 8b8aa940be..6439de3c1d 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -157,6 +157,7 @@ static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 	MK_FLOW_ITEM(L2TPV2, sizeof(struct rte_flow_item_l2tpv2)),
 	MK_FLOW_ITEM(PPP, sizeof(struct rte_flow_item_ppp)),
 	MK_FLOW_ITEM(METER_COLOR, sizeof(struct rte_flow_item_meter_color)),
+	MK_FLOW_ITEM(QUOTA, sizeof(struct rte_flow_item_quota)),
 };
 
 /** Generate flow_action[] entry. */
@@ -251,6 +252,7 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	MK_FLOW_ACTION(REPRESENTED_PORT, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(METER_MARK, sizeof(struct rte_flow_action_meter_mark)),
 	MK_FLOW_ACTION(SEND_TO_KERNEL, 0),
+	MK_FLOW_ACTION(QUOTA, sizeof(struct rte_flow_action_quota)),
 };
 
 int
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index f1ba163ac5..ed718057eb 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -624,7 +624,45 @@ enum rte_flow_item_type {
 	 * See struct rte_flow_item_meter_color.
 	 */
 	RTE_FLOW_ITEM_TYPE_METER_COLOR,
+
+	/**
+	 * Match Quota state
+	 *
+	 * @see struct rte_flow_item_quota
+	 */
+	 RTE_FLOW_ITEM_TYPE_QUOTA,
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * QUOTA state.
+ *
+ * @see struct rte_flow_item_quota
+ */
+enum rte_flow_quota_state {
+	RTE_FLOW_QUOTA_STATE_PASS, /** PASS quota state */
+	RTE_FLOW_QUOTA_STATE_BLOCK /** BLOCK quota state */
+};
+
+/**
+ * RTE_FLOW_ITEM_TYPE_QUOTA
+ *
+ * Matches QUOTA state
+ */
+struct rte_flow_item_quota {
+	enum rte_flow_quota_state state;
+};
+
+/**
+ * Default mask for RTE_FLOW_ITEM_TYPE_QUOTA
+ */
+#ifndef __cplusplus
+static const struct rte_flow_item_quota rte_flow_item_quota_mask = {
+	.state = (enum rte_flow_quota_state)0xff
 };
+#endif
 
 /**
  *
@@ -2736,6 +2774,81 @@ enum rte_flow_action_type {
 	 * No associated configuration structure.
 	 */
 	RTE_FLOW_ACTION_TYPE_SEND_TO_KERNEL,
+
+	/**
+	 * Apply quota verdict - PASS or BLOCK to a flow.
+	 *
+	 * @see struct rte_flow_action_quota
+	 * @see struct rte_flow_query_quota
+	 * @see struct rte_flow_update_quota
+	 */
+	 RTE_FLOW_ACTION_TYPE_QUOTA,
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * QUOTA operational mode.
+ *
+ * @see struct rte_flow_action_quota
+ */
+enum rte_flow_quota_mode {
+	RTE_FLOW_QUOTA_MODE_PACKET = 1, /** Count packets */
+	RTE_FLOW_QUOTA_MODE_L2 = 2, /** Count packet bytes starting from L2 */
+	RTE_FLOW_QUOTA_MODE_L3 = 3, /** Count packet bytes starting from L3 */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create QUOTA action.
+ *
+ * @see RTE_FLOW_ACTION_TYPE_QUOTA
+ */
+struct rte_flow_action_quota {
+	enum rte_flow_quota_mode mode; /** quota operational mode */
+	int64_t quota;                 /** quota value */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Query indirect QUOTA action.
+ *
+ * @see RTE_FLOW_ACTION_TYPE_QUOTA
+ *
+ */
+struct rte_flow_query_quota {
+	int64_t quota; /** quota value */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Indirect QUOTA update operations.
+ *
+ * @see struct rte_flow_update_quota
+ */
+enum rte_flow_update_quota_op {
+	RTE_FLOW_UPDATE_QUOTA_SET, /** set new quota value */
+	RTE_FLOW_UPDATE_QUOTA_ADD, /** increase existing quota with new value */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * @see RTE_FLOW_ACTION_TYPE_QUOTA
+ *
+ * Update indirect QUOTA action.
+ */
+struct rte_flow_update_quota {
+	enum rte_flow_update_quota_op op; /** update operation */
+	int64_t quota;                    /** quota value */
 };
 
 /**
@@ -4854,6 +4967,11 @@ struct rte_flow_port_info {
 	 * @see RTE_FLOW_ACTION_TYPE_CONNTRACK
 	 */
 	uint32_t max_nb_conn_tracks;
+	/**
+	 * Maximum number of quota actions.
+	 * @see RTE_FLOW_ACTION_TYPE_QUOTA
+	 */
+	uint32_t max_nb_quotas;
 	/**
 	 * Port supported flags (RTE_FLOW_PORT_FLAG_*).
 	 */
@@ -4932,6 +5050,11 @@ struct rte_flow_port_attr {
 	 * @see RTE_FLOW_ACTION_TYPE_CONNTRACK
 	 */
 	uint32_t nb_conn_tracks;
+	/**
+	 * Maximum number of quota actions.
+	 * @see RTE_FLOW_ACTION_TYPE_QUOTA
+	 */
+	uint32_t nb_quotas;
 	/**
 	 * Port flags (RTE_FLOW_PORT_FLAG_*).
 	 */
