All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
To: dev@dpdk.org
Subject: [dpdk-dev] [PATCH 2/4] pipeline: add support for learner tables
Date: Sat, 14 Aug 2021 00:52:30 +0100	[thread overview]
Message-ID: <20210813235232.65757-2-cristian.dumitrescu@intel.com> (raw)
In-Reply-To: <20210813235232.65757-1-cristian.dumitrescu@intel.com>

Add pipeline level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.c           | 479 ++++++++++++-
 lib/pipeline/rte_swx_ctl.h           | 185 +++++
 lib/pipeline/rte_swx_pipeline.c      | 988 +++++++++++++++++++++++++--
 lib/pipeline/rte_swx_pipeline.h      |  77 +++
 lib/pipeline/rte_swx_pipeline_spec.c | 470 ++++++++++++-
 lib/pipeline/version.map             |   8 +
 6 files changed, 2136 insertions(+), 71 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c
index dc093860de..86b58e21dc 100644
--- a/lib/pipeline/rte_swx_ctl.c
+++ b/lib/pipeline/rte_swx_ctl.c
@@ -123,12 +123,26 @@ struct selector {
 	struct rte_swx_table_selector_params params;
 };
 
+struct learner {
+	struct rte_swx_ctl_learner_info info;
+	struct rte_swx_ctl_table_match_field_info *mf;
+	struct rte_swx_ctl_table_action_info *actions;
+	uint32_t action_data_size;
+
+	/* The pending default action: this is NOT the current default action;
+	 * this will be the new default action after the next commit, if the
+	 * next commit operation is successful.
+	 */
+	struct rte_swx_table_entry *pending_default;
+};
+
 struct rte_swx_ctl_pipeline {
 	struct rte_swx_ctl_pipeline_info info;
 	struct rte_swx_pipeline *p;
 	struct action *actions;
 	struct table *tables;
 	struct selector *selectors;
+	struct learner *learners;
 	struct rte_swx_table_state *ts;
 	struct rte_swx_table_state *ts_next;
 	int numa_node;
@@ -924,6 +938,70 @@ selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
 	return 0;
 }
 
+static void
+learner_pending_default_free(struct learner *l)
+{
+	if (!l->pending_default)
+		return;
+
+	free(l->pending_default->action_data);
+	free(l->pending_default);
+	l->pending_default = NULL;
+}
+
+
+static void
+learner_free(struct rte_swx_ctl_pipeline *ctl)
+{
+	uint32_t i;
+
+	if (!ctl->learners)
+		return;
+
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		struct learner *l = &ctl->learners[i];
+
+		free(l->mf);
+		free(l->actions);
+
+		learner_pending_default_free(l);
+	}
+
+	free(ctl->learners);
+	ctl->learners = NULL;
+}
+
+static struct learner *
+learner_find(struct rte_swx_ctl_pipeline *ctl, const char *learner_name)
+{
+	uint32_t i;
+
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		struct learner *l = &ctl->learners[i];
+
+		if (!strcmp(learner_name, l->info.name))
+			return l;
+	}
+
+	return NULL;
+}
+
+static uint32_t
+learner_action_data_size_get(struct rte_swx_ctl_pipeline *ctl, struct learner *l)
+{
+	uint32_t action_data_size = 0, i;
+
+	for (i = 0; i < l->info.n_actions; i++) {
+		uint32_t action_id = l->actions[i].action_id;
+		struct action *a = &ctl->actions[action_id];
+
+		if (a->data_size > action_data_size)
+			action_data_size = a->data_size;
+	}
+
+	return action_data_size;
+}
+
 static void
 table_state_free(struct rte_swx_ctl_pipeline *ctl)
 {
@@ -954,6 +1032,14 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)
 			rte_swx_table_selector_free(ts->obj);
 	}
 
+	/* For each learner table, free its table state. */
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		struct rte_swx_table_state *ts = &ctl->ts_next[i];
+
+		/* Default action data. */
+		free(ts->default_action_data);
+	}
+
 	free(ctl->ts_next);
 	ctl->ts_next = NULL;
 }
@@ -1020,6 +1106,29 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 		}
 	}
 
+	/* Learner tables. */
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		struct learner *l = &ctl->learners[i];
+		struct rte_swx_table_state *ts = &ctl->ts[i];
+		struct rte_swx_table_state *ts_next = &ctl->ts_next[i];
+
+		/* Table object: duplicate from the current table state. */
+		ts_next->obj = ts->obj;
+
+		/* Default action data: duplicate from the current table state. */
+		ts_next->default_action_data = malloc(l->action_data_size);
+		if (!ts_next->default_action_data) {
+			status = -ENOMEM;
+			goto error;
+		}
+
+		memcpy(ts_next->default_action_data,
+		       ts->default_action_data,
+		       l->action_data_size);
+
+		ts_next->default_action_id = ts->default_action_id;
+	}
+
 	return 0;
 
 error:
@@ -1037,6 +1146,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)
 
 	table_state_free(ctl);
 
+	learner_free(ctl);
+
 	selector_free(ctl);
 
 	table_free(ctl);
@@ -1251,6 +1362,54 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)
 			goto error;
 	}
 
+	/* learner tables. */
+	ctl->learners = calloc(ctl->info.n_learners, sizeof(struct learner));
+	if (!ctl->learners)
+		goto error;
+
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		struct learner *l = &ctl->learners[i];
+		uint32_t j;
+
+		/* info. */
+		status = rte_swx_ctl_learner_info_get(p, i, &l->info);
+		if (status)
+			goto error;
+
+		/* mf. */
+		l->mf = calloc(l->info.n_match_fields,
+			       sizeof(struct rte_swx_ctl_table_match_field_info));
+		if (!l->mf)
+			goto error;
+
+		for (j = 0; j < l->info.n_match_fields; j++) {
+			status = rte_swx_ctl_learner_match_field_info_get(p,
+				i,
+				j,
+				&l->mf[j]);
+			if (status)
+				goto error;
+		}
+
+		/* actions. */
+		l->actions = calloc(l->info.n_actions,
+			sizeof(struct rte_swx_ctl_table_action_info));
+		if (!l->actions)
+			goto error;
+
+		for (j = 0; j < l->info.n_actions; j++) {
+			status = rte_swx_ctl_learner_action_info_get(p,
+				i,
+				j,
+				&l->actions[j]);
+			if (status || l->actions[j].action_id >= ctl->info.n_actions)
+				goto error;
+		}
+
+		/* action_data_size. */
+		l->action_data_size = learner_action_data_size_get(ctl, l);
+	}
+
 	/* ts. */
 	status = rte_swx_pipeline_table_state_get(p, &ctl->ts);
 	if (status)
@@ -1685,9 +1844,8 @@ table_rollfwd1(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)
 	action_data = table->pending_default->action_data;
 	a = &ctl->actions[action_id];
 
-	memcpy(ts_next->default_action_data,
-	       action_data,
-	       a->data_size);
+	if (a->data_size)
+		memcpy(ts_next->default_action_data, action_data, a->data_size);
 
 	ts_next->default_action_id = action_id;
 }
@@ -2099,6 +2257,178 @@ selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
 	memset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));
 }
 
+static struct rte_swx_table_entry *
+learner_default_entry_alloc(struct learner *l)
+{
+	struct rte_swx_table_entry *entry;
+
+	entry = calloc(1, sizeof(struct rte_swx_table_entry));
+	if (!entry)
+		goto error;
+
+	/* action_data. */
+	if (l->action_data_size) {
+		entry->action_data = calloc(1, l->action_data_size);
+		if (!entry->action_data)
+			goto error;
+	}
+
+	return entry;
+
+error:
+	table_entry_free(entry);
+	return NULL;
+}
+
+static int
+learner_default_entry_check(struct rte_swx_ctl_pipeline *ctl,
+			    uint32_t learner_id,
+			    struct rte_swx_table_entry *entry)
+{
+	struct learner *l = &ctl->learners[learner_id];
+	struct action *a;
+	uint32_t i;
+
+	CHECK(entry, EINVAL);
+
+	/* action_id. */
+	for (i = 0; i < l->info.n_actions; i++)
+		if (entry->action_id == l->actions[i].action_id)
+			break;
+
+	CHECK(i < l->info.n_actions, EINVAL);
+
+	/* action_data. */
+	a = &ctl->actions[entry->action_id];
+	CHECK(!(a->data_size && !entry->action_data), EINVAL);
+
+	return 0;
+}
+
+static struct rte_swx_table_entry *
+learner_default_entry_duplicate(struct rte_swx_ctl_pipeline *ctl,
+				uint32_t learner_id,
+				struct rte_swx_table_entry *entry)
+{
+	struct learner *l = &ctl->learners[learner_id];
+	struct rte_swx_table_entry *new_entry = NULL;
+	struct action *a;
+	uint32_t i;
+
+	if (!entry)
+		goto error;
+
+	new_entry = calloc(1, sizeof(struct rte_swx_table_entry));
+	if (!new_entry)
+		goto error;
+
+	/* action_id. */
+	for (i = 0; i < l->info.n_actions; i++)
+		if (entry->action_id == l->actions[i].action_id)
+			break;
+
+	if (i >= l->info.n_actions)
+		goto error;
+
+	new_entry->action_id = entry->action_id;
+
+	/* action_data. */
+	a = &ctl->actions[entry->action_id];
+	if (a->data_size && !entry->action_data)
+		goto error;
+
+	/* The table layer provisions a constant action data size per
+	 * entry, which should be the largest data size for all the
+	 * actions enabled for the current table, and attempts to copy
+	 * this many bytes each time a table entry is added, even if the
+	 * specific action requires less data or even no data at all,
+	 * hence we always have to allocate the max.
+	 */
+	new_entry->action_data = calloc(1, l->action_data_size);
+	if (!new_entry->action_data)
+		goto error;
+
+	if (a->data_size)
+		memcpy(new_entry->action_data, entry->action_data, a->data_size);
+
+	return new_entry;
+
+error:
+	table_entry_free(new_entry);
+	return NULL;
+}
+
+int
+rte_swx_ctl_pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *learner_name,
+					       struct rte_swx_table_entry *entry)
+{
+	struct learner *l;
+	struct rte_swx_table_entry *new_entry;
+	uint32_t learner_id;
+
+	CHECK(ctl, EINVAL);
+
+	CHECK(learner_name && learner_name[0], EINVAL);
+	l = learner_find(ctl, learner_name);
+	CHECK(l, EINVAL);
+	learner_id = l - ctl->learners;
+	CHECK(!l->info.default_action_is_const, EINVAL);
+
+	CHECK(entry, EINVAL);
+	CHECK(!learner_default_entry_check(ctl, learner_id, entry), EINVAL);
+
+	new_entry = learner_default_entry_duplicate(ctl, learner_id, entry);
+	CHECK(new_entry, ENOMEM);
+
+	learner_pending_default_free(l);
+
+	l->pending_default = new_entry;
+	return 0;
+}
+
+static void
+learner_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+	struct learner *l = &ctl->learners[learner_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables +
+		ctl->info.n_selectors + learner_id];
+	struct action *a;
+	uint8_t *action_data;
+	uint64_t action_id;
+
+	/* Copy the pending default entry. */
+	if (!l->pending_default)
+		return;
+
+	action_id = l->pending_default->action_id;
+	action_data = l->pending_default->action_data;
+	a = &ctl->actions[action_id];
+
+	if (a->data_size)
+		memcpy(ts_next->default_action_data, action_data, a->data_size);
+
+	ts_next->default_action_id = action_id;
+}
+
+static void
+learner_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+	struct learner *l = &ctl->learners[learner_id];
+
+	/* Free up the pending default entry, as it is now part of the table. */
+	learner_pending_default_free(l);
+}
+
+static void
+learner_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+	struct learner *l = &ctl->learners[learner_id];
+
+	/* Free up the pending default entry, as it is no longer going to be added to the table. */
+	learner_pending_default_free(l);
+}
+
 int
 rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 {
@@ -2110,6 +2440,7 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 
 	/* Operate the changes on the current ts_next before it becomes the new ts. First, operate
 	 * all the changes that can fail; if no failure, then operate the changes that cannot fail.
+	 * We must be able to fully revert all the changes that can fail as if they never happened.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		status = table_rollfwd0(ctl, i, 0);
@@ -2123,9 +2454,15 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			goto rollback;
 	}
 
+	/* Second, operate all the changes that cannot fail. Since nothing can fail from this point
+	 * onwards, the transaction is guaranteed to be successful.
+	 */
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_rollfwd1(ctl, i);
 
+	for (i = 0; i < ctl->info.n_learners; i++)
+		learner_rollfwd(ctl, i);
+
 	/* Swap the table state for the data plane. The current ts and ts_next
 	 * become the new ts_next and ts, respectively.
 	 */
@@ -2151,6 +2488,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 		selector_rollfwd_finalize(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_learners; i++) {
+		learner_rollfwd(ctl, i);
+		learner_rollfwd_finalize(ctl, i);
+	}
+
 	return 0;
 
 rollback:
@@ -2166,6 +2508,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			selector_abort(ctl, i);
 	}
 
+	if (abort_on_fail)
+		for (i = 0; i < ctl->info.n_learners; i++)
+			learner_abort(ctl, i);
+
 	return status;
 }
 
@@ -2182,6 +2528,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)
 
 	for (i = 0; i < ctl->info.n_selectors; i++)
 		selector_abort(ctl, i);
+
+	for (i = 0; i < ctl->info.n_learners; i++)
+		learner_abort(ctl, i);
 }
 
 static int
@@ -2460,6 +2809,130 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 	return NULL;
 }
 
+struct rte_swx_table_entry *
+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,
+						const char *learner_name,
+						const char *string,
+						int *is_blank_or_comment)
+{
+	char *token_array[RTE_SWX_CTL_ENTRY_TOKENS_MAX], **tokens;
+	struct learner *l;
+	struct action *action;
+	struct rte_swx_table_entry *entry = NULL;
+	char *s0 = NULL, *s;
+	uint32_t n_tokens = 0, arg_offset = 0, i;
+	int blank_or_comment = 0;
+
+	/* Check input arguments. */
+	if (!ctl)
+		goto error;
+
+	if (!learner_name || !learner_name[0])
+		goto error;
+
+	l = learner_find(ctl, learner_name);
+	if (!l)
+		goto error;
+
+	if (!string || !string[0])
+		goto error;
+
+	/* Memory allocation. */
+	s0 = strdup(string);
+	if (!s0)
+		goto error;
+
+	entry = learner_default_entry_alloc(l);
+	if (!entry)
+		goto error;
+
+	/* Parse the string into tokens. */
+	for (s = s0; ; ) {
+		char *token;
+
+		token = strtok_r(s, " \f\n\r\t\v", &s);
+		if (!token || token_is_comment(token))
+			break;
+
+		if (n_tokens >= RTE_SWX_CTL_ENTRY_TOKENS_MAX)
+			goto error;
+
+		token_array[n_tokens] = token;
+		n_tokens++;
+	}
+
+	if (!n_tokens) {
+		blank_or_comment = 1;
+		goto error;
+	}
+
+	tokens = token_array;
+
+	/*
+	 * Action.
+	 */
+	if (!(n_tokens && !strcmp(tokens[0], "action")))
+		goto other;
+
+	if (n_tokens < 2)
+		goto error;
+
+	action = action_find(ctl, tokens[1]);
+	if (!action)
+		goto error;
+
+	if (n_tokens < 2 + action->info.n_args * 2)
+		goto error;
+
+	/* action_id. */
+	entry->action_id = action - ctl->actions;
+
+	/* action_data. */
+	for (i = 0; i < action->info.n_args; i++) {
+		struct rte_swx_ctl_action_arg_info *arg = &action->args[i];
+		char *arg_name, *arg_val;
+		uint64_t val;
+
+		arg_name = tokens[2 + i * 2];
+		arg_val = tokens[2 + i * 2 + 1];
+
+		if (strcmp(arg_name, arg->name))
+			goto error;
+
+		val = strtoull(arg_val, &arg_val, 0);
+		if (arg_val[0])
+			goto error;
+
+		/* Endianness conversion. */
+		if (arg->is_network_byte_order)
+			val = field_hton(val, arg->n_bits);
+
+		/* Copy to entry. */
+		memcpy(&entry->action_data[arg_offset],
+		       (uint8_t *)&val,
+		       arg->n_bits / 8);
+
+		arg_offset += arg->n_bits / 8;
+	}
+
+	tokens += 2 + action->info.n_args * 2;
+	n_tokens -= 2 + action->info.n_args * 2;
+
+other:
+	if (n_tokens)
+		goto error;
+
+	free(s0);
+	return entry;
+
+error:
+	table_entry_free(entry);
+	free(s0);
+	if (is_blank_or_comment)
+		*is_blank_or_comment = blank_or_comment;
+	return NULL;
+}
+
 static void
 table_entry_printf(FILE *f,
 		   struct rte_swx_ctl_pipeline *ctl,
diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index f37301cf95..2a7d1d57ce 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -52,6 +52,9 @@ struct rte_swx_ctl_pipeline_info {
 	/** Number of selector tables. */
 	uint32_t n_selectors;
 
+	/** Number of learner tables. */
+	uint32_t n_learners;
+
 	/** Number of register arrays. */
 	uint32_t n_regarrays;
 
@@ -512,6 +515,142 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
 					 const char *selector_name,
 					 struct rte_swx_pipeline_selector_stats *stats);
 
+/*
+ * Learner Table Query API.
+ */
+
+/** Learner table info. */
+struct rte_swx_ctl_learner_info {
+	/** Learner table name. */
+	char name[RTE_SWX_CTL_NAME_SIZE];
+
+	/** Number of match fields. */
+	uint32_t n_match_fields;
+
+	/** Number of actions. */
+	uint32_t n_actions;
+
+	/** Non-zero (true) when the default action is constant, therefore it
+	 * cannot be changed; zero (false) when the default action not constant,
+	 * therefore it can be changed.
+	 */
+	int default_action_is_const;
+
+	/** Learner table size parameter. */
+	uint32_t size;
+};
+
+/**
+ * Learner table info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_id
+ *   Learner table ID (0 .. *n_learners* - 1).
+ * @param[out] learner
+ *   Learner table info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,
+			     uint32_t learner_id,
+			     struct rte_swx_ctl_learner_info *learner);
+
+/**
+ * Learner table match field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_id
+ *   Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] match_field_id
+ *   Match field ID (0 .. *n_match_fields* - 1).
+ * @param[out] match_field
+ *   Learner table match field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t match_field_id,
+					 struct rte_swx_ctl_table_match_field_info *match_field);
+
+/**
+ * Learner table action info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_id
+ *   Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] learner_action_id
+ *   Action index within the set of learner table actions (0 .. learner table n_actions - 1). Not
+ *   to be confused with the pipeline-leve action ID (0 .. pipeline n_actions - 1), which is
+ *   precisely what this function returns as part of the *learner_action*.
+ * @param[out] learner_action
+ *   Learner action info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
+				    uint32_t learner_id,
+				    uint32_t learner_action_id,
+				    struct rte_swx_ctl_table_action_info *learner_action);
+
+/** Learner table statistics. */
+struct rte_swx_learner_stats {
+	/** Number of packets with lookup hit. */
+	uint64_t n_pkts_hit;
+
+	/** Number of packets with lookup miss. */
+	uint64_t n_pkts_miss;
+
+	/** Number of packets with successful learning. */
+	uint64_t n_pkts_learn_ok;
+
+	/** Number of packets with learning error. */
+	uint64_t n_pkts_learn_err;
+
+	/** Number of packets with forget event. */
+	uint64_t n_pkts_forget;
+
+	/** Number of packets (with either lookup hit or miss) per pipeline action. Array of
+	 * pipeline *n_actions* elements indedex by the pipeline-level *action_id*, therefore this
+	 * array has the same size for all the tables within the same pipeline.
+	 */
+	uint64_t *n_pkts_action;
+};
+
+/**
+ * Learner table statistics counters read
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_name
+ *   Learner table name.
+ * @param[out] stats
+ *   Learner table stats. Must point to a pre-allocated structure. The *n_pkts_action* field also
+ *   needs to be pre-allocated as array of pipeline *n_actions* elements. The pipeline actions that
+ *   are not valid for the current learner table have their associated *n_pkts_action* element
+ *   always set to zero.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
+				      const char *learner_name,
+				      struct rte_swx_learner_stats *stats);
+
 /*
  * Table Update API.
  */
@@ -761,6 +900,27 @@ rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *c
 						  uint32_t group_id,
 						  uint32_t member_id);
 
+/**
+ * Pipeline learner table default entry add
+ *
+ * Schedule learner table default entry update as part of the next commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] learner_name
+ *   Learner table name.
+ * @param[in] entry
+ *   The new table default entry. The *key* and *key_mask* entry fields are ignored.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *learner_name,
+					       struct rte_swx_table_entry *entry);
+
 /**
  * Pipeline commit
  *
@@ -819,6 +979,31 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 				      const char *string,
 				      int *is_blank_or_comment);
 
+/**
+ * Pipeline learner table default entry read
+ *
+ * Read learner table default entry from string.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] learner_name
+ *   Learner table name.
+ * @param[in] string
+ *   String containing the learner table default entry.
+ * @param[out] is_blank_or_comment
+ *   On error, this argument provides an indication of whether *string* contains
+ *   an invalid table entry (set to zero) or a blank or comment line that should
+ *   typically be ignored (set to a non-zero value).
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+struct rte_swx_table_entry *
+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,
+						const char *learner_name,
+						const char *string,
+						int *is_blank_or_comment);
+
 /**
  * Pipeline table print to file
  *
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index 13028bcc6a..7caf96ad75 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -16,6 +16,7 @@
 #include <rte_meter.h>
 
 #include <rte_swx_table_selector.h>
+#include <rte_swx_table_learner.h>
 
 #include "rte_swx_pipeline.h"
 #include "rte_swx_ctl.h"
@@ -511,6 +512,13 @@ enum instruction_type {
 	/* table TABLE */
 	INSTR_TABLE,
 	INSTR_SELECTOR,
+	INSTR_LEARNER,
+
+	/* learn LEARNER ACTION_NAME */
+	INSTR_LEARNER_LEARN,
+
+	/* forget */
+	INSTR_LEARNER_FORGET,
 
 	/* extern e.obj.func */
 	INSTR_EXTERN_OBJ,
@@ -636,6 +644,10 @@ struct instr_table {
 	uint8_t table_id;
 };
 
+struct instr_learn {
+	uint8_t action_id;
+};
+
 struct instr_extern_obj {
 	uint8_t ext_obj_id;
 	uint8_t func_id;
@@ -726,6 +738,7 @@ struct instruction {
 		struct instr_dma dma;
 		struct instr_dst_src alu;
 		struct instr_table table;
+		struct instr_learn learn;
 		struct instr_extern_obj ext_obj;
 		struct instr_extern_func ext_func;
 		struct instr_jmp jmp;
@@ -839,6 +852,47 @@ struct selector_statistics {
 	uint64_t n_pkts;
 };
 
+/*
+ * Learner table.
+ */
+struct learner {
+	TAILQ_ENTRY(learner) node;
+	char name[RTE_SWX_NAME_SIZE];
+
+	/* Match. */
+	struct field **fields;
+	uint32_t n_fields;
+	struct header *header;
+
+	/* Action. */
+	struct action **actions;
+	struct field **action_arg;
+	struct action *default_action;
+	uint8_t *default_action_data;
+	uint32_t n_actions;
+	int default_action_is_const;
+	uint32_t action_data_size_max;
+
+	uint32_t size;
+	uint32_t timeout;
+	uint32_t id;
+};
+
+TAILQ_HEAD(learner_tailq, learner);
+
+struct learner_runtime {
+	void *mailbox;
+	uint8_t **key;
+	uint8_t **action_data;
+};
+
+struct learner_statistics {
+	uint64_t n_pkts_hit[2]; /* 0 = Miss, 1 = Hit. */
+	uint64_t n_pkts_learn[2]; /* 0 = Learn OK, 1 = Learn error. */
+	uint64_t n_pkts_forget;
+	uint64_t *n_pkts_action;
+};
+
 /*
  * Register array.
  */
@@ -919,9 +973,12 @@ struct thread {
 	/* Tables. */
 	struct table_runtime *tables;
 	struct selector_runtime *selectors;
+	struct learner_runtime *learners;
 	struct rte_swx_table_state *table_state;
 	uint64_t action_id;
 	int hit; /* 0 = Miss, 1 = Hit. */
+	uint32_t learner_id;
+	uint64_t time;
 
 	/* Extern objects and functions. */
 	struct extern_obj_runtime *extern_objs;
@@ -1355,6 +1412,7 @@ struct rte_swx_pipeline {
 	struct table_type_tailq table_types;
 	struct table_tailq tables;
 	struct selector_tailq selectors;
+	struct learner_tailq learners;
 	struct regarray_tailq regarrays;
 	struct meter_profile_tailq meter_profiles;
 	struct metarray_tailq metarrays;
@@ -1365,6 +1423,7 @@ struct rte_swx_pipeline {
 	struct rte_swx_table_state *table_state;
 	struct table_statistics *table_stats;
 	struct selector_statistics *selector_stats;
+	struct learner_statistics *learner_stats;
 	struct regarray_runtime *regarray_runtime;
 	struct metarray_runtime *metarray_runtime;
 	struct instruction *instructions;
@@ -1378,6 +1437,7 @@ struct rte_swx_pipeline {
 	uint32_t n_actions;
 	uint32_t n_tables;
 	uint32_t n_selectors;
+	uint32_t n_learners;
 	uint32_t n_regarrays;
 	uint32_t n_metarrays;
 	uint32_t n_headers;
@@ -3625,6 +3685,9 @@ table_find(struct rte_swx_pipeline *p, const char *name);
 static struct selector *
 selector_find(struct rte_swx_pipeline *p, const char *name);
 
+static struct learner *
+learner_find(struct rte_swx_pipeline *p, const char *name);
+
 static int
 instr_table_translate(struct rte_swx_pipeline *p,
 		      struct action *action,
@@ -3635,6 +3698,7 @@ instr_table_translate(struct rte_swx_pipeline *p,
 {
 	struct table *t;
 	struct selector *s;
+	struct learner *l;
 
 	CHECK(!action, EINVAL);
 	CHECK(n_tokens == 2, EINVAL);
@@ -3653,6 +3717,13 @@ instr_table_translate(struct rte_swx_pipeline *p,
 		return 0;
 	}
 
+	l = learner_find(p, tokens[1]);
+	if (l) {
+		instr->type = INSTR_LEARNER;
+		instr->table.table_id = l->id;
+		return 0;
+	}
+
 	CHECK(0, EINVAL);
 }
 
@@ -3746,6 +3817,164 @@ instr_selector_exec(struct rte_swx_pipeline *p)
 	thread_ip_inc(p);
 }
 
+static inline void
+instr_learner_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+	uint32_t learner_id = ip->table.table_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+		p->n_selectors + learner_id];
+	struct learner_runtime *l = &t->learners[learner_id];
+	struct learner_statistics *stats = &p->learner_stats[learner_id];
+	uint64_t action_id, n_pkts_hit, n_pkts_action, time;
+	uint8_t *action_data;
+	int done, hit;
+
+	/* Table. */
+	time = rte_get_tsc_cycles();
+
+	done = rte_swx_table_learner_lookup(ts->obj,
+					    l->mailbox,
+					    time,
+					    l->key,
+					    &action_id,
+					    &action_data,
+					    &hit);
+	if (!done) {
+		/* Thread. */
+		TRACE("[Thread %2u] learner %u (not finalized)\n",
+		      p->thread_id,
+		      learner_id);
+
+		thread_yield(p);
+		return;
+	}
+
+	action_id = hit ? action_id : ts->default_action_id;
+	action_data = hit ? action_data : ts->default_action_data;
+	n_pkts_hit = stats->n_pkts_hit[hit];
+	n_pkts_action = stats->n_pkts_action[action_id];
+
+	TRACE("[Thread %2u] learner %u (%s, action %u)\n",
+	      p->thread_id,
+	      learner_id,
+	      hit ? "hit" : "miss",
+	      (uint32_t)action_id);
+
+	t->action_id = action_id;
+	t->structs[0] = action_data;
+	t->hit = hit;
+	t->learner_id = learner_id;
+	t->time = time;
+	stats->n_pkts_hit[hit] = n_pkts_hit + 1;
+	stats->n_pkts_action[action_id] = n_pkts_action + 1;
+
+	/* Thread. */
+	thread_ip_action_call(p, t, action_id);
+}
+
+/*
+ * learn.
+ */
+static struct action *
+action_find(struct rte_swx_pipeline *p, const char *name);
+
+static int
+instr_learn_translate(struct rte_swx_pipeline *p,
+		      struct action *action,
+		      char **tokens,
+		      int n_tokens,
+		      struct instruction *instr,
+		      struct instruction_data *data __rte_unused)
+{
+	struct action *a;
+
+	CHECK(action, EINVAL);
+	CHECK(n_tokens == 2, EINVAL);
+
+	a = action_find(p, tokens[1]);
+	CHECK(a, EINVAL);
+
+	instr->type = INSTR_LEARNER_LEARN;
+	instr->learn.action_id = a->id;
+
+	return 0;
+}
+
+static inline void
+instr_learn_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+	uint64_t action_id = ip->learn.action_id;
+	uint32_t learner_id = t->learner_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+		p->n_selectors + learner_id];
+	struct learner_runtime *l = &t->learners[learner_id];
+	struct learner_statistics *stats = &p->learner_stats[learner_id];
+	uint32_t status;
+
+	/* Table. */
+	status = rte_swx_table_learner_add(ts->obj,
+					   l->mailbox,
+					   t->time,
+					   action_id,
+					   l->action_data[action_id]);
+
+	TRACE("[Thread %2u] learner %u learn %s\n",
+	      p->thread_id,
+	      learner_id,
+	      status ? "ok" : "error");
+
+	stats->n_pkts_learn[status] += 1;
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
+/*
+ * forget.
+ */
+static int
+instr_forget_translate(struct rte_swx_pipeline *p __rte_unused,
+		       struct action *action,
+		       char **tokens __rte_unused,
+		       int n_tokens,
+		       struct instruction *instr,
+		       struct instruction_data *data __rte_unused)
+{
+	CHECK(action, EINVAL);
+	CHECK(n_tokens == 1, EINVAL);
+
+	instr->type = INSTR_LEARNER_FORGET;
+
+	return 0;
+}
+
+static inline void
+instr_forget_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	uint32_t learner_id = t->learner_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+		p->n_selectors + learner_id];
+	struct learner_runtime *l = &t->learners[learner_id];
+	struct learner_statistics *stats = &p->learner_stats[learner_id];
+
+	/* Table. */
+	rte_swx_table_learner_delete(ts->obj, l->mailbox);
+
+	TRACE("[Thread %2u] learner %u forget\n",
+	      p->thread_id,
+	      learner_id);
+
+	stats->n_pkts_forget += 1;
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * extern.
  */
@@ -7159,9 +7388,6 @@ instr_meter_imi_exec(struct rte_swx_pipeline *p)
 /*
  * jmp.
  */
-static struct action *
-action_find(struct rte_swx_pipeline *p, const char *name);
-
 static int
 instr_jmp_translate(struct rte_swx_pipeline *p __rte_unused,
 		    struct action *action __rte_unused,
@@ -8136,6 +8362,22 @@ instr_translate(struct rte_swx_pipeline *p,
 					     instr,
 					     data);
 
+	if (!strcmp(tokens[tpos], "learn"))
+		return instr_learn_translate(p,
+					     action,
+					     &tokens[tpos],
+					     n_tokens - tpos,
+					     instr,
+					     data);
+
+	if (!strcmp(tokens[tpos], "forget"))
+		return instr_forget_translate(p,
+					      action,
+					      &tokens[tpos],
+					      n_tokens - tpos,
+					      instr,
+					      data);
+
 	if (!strcmp(tokens[tpos], "extern"))
 		return instr_extern_translate(p,
 					      action,
@@ -9096,6 +9338,9 @@ static instr_exec_t instruction_table[] = {
 
 	[INSTR_TABLE] = instr_table_exec,
 	[INSTR_SELECTOR] = instr_selector_exec,
+	[INSTR_LEARNER] = instr_learner_exec,
+	[INSTR_LEARNER_LEARN] = instr_learn_exec,
+	[INSTR_LEARNER_FORGET] = instr_forget_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
 
@@ -9546,6 +9791,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 	CHECK_NAME(name, EINVAL);
 	CHECK(!table_find(p, name), EEXIST);
 	CHECK(!selector_find(p, name), EEXIST);
+	CHECK(!learner_find(p, name), EEXIST);
 
 	CHECK(params, EINVAL);
 
@@ -9964,6 +10210,7 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
 	CHECK_NAME(name, EINVAL);
 	CHECK(!table_find(p, name), EEXIST);
 	CHECK(!selector_find(p, name), EEXIST);
+	CHECK(!learner_find(p, name), EEXIST);
 
 	CHECK(params, EINVAL);
 
@@ -10221,98 +10468,596 @@ selector_free(struct rte_swx_pipeline *p)
 }
 
 /*
- * Table state.
+ * Learner table.
  */
-static int
-table_state_build(struct rte_swx_pipeline *p)
+static struct learner *
+learner_find(struct rte_swx_pipeline *p, const char *name)
 {
-	struct table *table;
-	struct selector *s;
-
-	p->table_state = calloc(p->n_tables + p->n_selectors,
-				sizeof(struct rte_swx_table_state));
-	CHECK(p->table_state, ENOMEM);
+	struct learner *l;
 
-	TAILQ_FOREACH(table, &p->tables, node) {
-		struct rte_swx_table_state *ts = &p->table_state[table->id];
+	TAILQ_FOREACH(l, &p->learners, node)
+		if (!strcmp(l->name, name))
+			return l;
 
-		if (table->type) {
-			struct rte_swx_table_params *params;
+	return NULL;
+}
 
-			/* ts->obj. */
-			params = table_params_get(table);
-			CHECK(params, ENOMEM);
+static struct learner *
+learner_find_by_id(struct rte_swx_pipeline *p, uint32_t id)
+{
+	struct learner *l = NULL;
 
-			ts->obj = table->type->ops.create(params,
-				NULL,
-				table->args,
-				p->numa_node);
+	TAILQ_FOREACH(l, &p->learners, node)
+		if (l->id == id)
+			return l;
 
-			table_params_free(params);
-			CHECK(ts->obj, ENODEV);
-		}
+	return NULL;
+}
 
-		/* ts->default_action_data. */
-		if (table->action_data_size_max) {
-			ts->default_action_data =
-				malloc(table->action_data_size_max);
-			CHECK(ts->default_action_data, ENOMEM);
+static int
+learner_match_fields_check(struct rte_swx_pipeline *p,
+			   struct rte_swx_pipeline_learner_params *params,
+			   struct header **header)
+{
+	struct header *h0 = NULL;
+	struct field *hf, *mf;
+	uint32_t i;
 
-			memcpy(ts->default_action_data,
-			       table->default_action_data,
-			       table->action_data_size_max);
-		}
+	/* Return if no match fields. */
+	if (!params->n_fields || !params->field_names)
+		return -EINVAL;
 
-		/* ts->default_action_id. */
-		ts->default_action_id = table->default_action->id;
-	}
+	/* Check that all the match fields either belong to the same header
+	 * or are all meta-data fields.
+	 */
+	hf = header_field_parse(p, params->field_names[0], &h0);
+	mf = metadata_field_parse(p, params->field_names[0]);
+	if (!hf && !mf)
+		return -EINVAL;
 
-	TAILQ_FOREACH(s, &p->selectors, node) {
-		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
-		struct rte_swx_table_selector_params *params;
+	for (i = 1; i < params->n_fields; i++)
+		if (h0) {
+			struct header *h;
 
-		/* ts->obj. */
-		params = selector_table_params_get(s);
-		CHECK(params, ENOMEM);
+			hf = header_field_parse(p, params->field_names[i], &h);
+			if (!hf || (h->id != h0->id))
+				return -EINVAL;
+		} else {
+			mf = metadata_field_parse(p, params->field_names[i]);
+			if (!mf)
+				return -EINVAL;
+		}
 
-		ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+	/* Check that there are no duplicated match fields. */
+	for (i = 0; i < params->n_fields; i++) {
+		const char *field_name = params->field_names[i];
+		uint32_t j;
 
-		selector_params_free(params);
-		CHECK(ts->obj, ENODEV);
+		for (j = i + 1; j < params->n_fields; j++)
+			if (!strcmp(params->field_names[j], field_name))
+				return -EINVAL;
 	}
 
+	/* Return. */
+	if (header)
+		*header = h0;
+
 	return 0;
 }
 
-static void
-table_state_build_free(struct rte_swx_pipeline *p)
+static int
+learner_action_args_check(struct rte_swx_pipeline *p, struct action *a, const char *mf_name)
 {
-	uint32_t i;
+	struct struct_type *mst = p->metadata_st, *ast = a->st;
+	struct field *mf, *af;
+	uint32_t mf_pos, i;
 
-	if (!p->table_state)
-		return;
+	if (!ast) {
+		if (mf_name)
+			return -EINVAL;
 
-	for (i = 0; i < p->n_tables; i++) {
-		struct rte_swx_table_state *ts = &p->table_state[i];
-		struct table *table = table_find_by_id(p, i);
+		return 0;
+	}
 
-		/* ts->obj. */
-		if (table->type && ts->obj)
-			table->type->ops.free(ts->obj);
+	/* Check that mf_name is the name of a valid meta-data field. */
+	CHECK_NAME(mf_name, EINVAL);
+	mf = metadata_field_parse(p, mf_name);
+	CHECK(mf, EINVAL);
 
-		/* ts->default_action_data. */
-		free(ts->default_action_data);
-	}
+	/* Check that there are enough meta-data fields, starting with the mf_name field, to cover
+	 * all the action arguments.
+	 */
+	mf_pos = mf - mst->fields;
+	CHECK(mst->n_fields - mf_pos >= ast->n_fields, EINVAL);
 
-	for (i = 0; i < p->n_selectors; i++) {
-		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];
+	/* Check that the size of each of the identified meta-data fields matches exactly the size
+	 * of the corresponding action argument.
+	 */
+	for (i = 0; i < ast->n_fields; i++) {
+		mf = &mst->fields[mf_pos + i];
+		af = &ast->fields[i];
 
-		/* ts->obj. */
-		if (ts->obj)
-			rte_swx_table_selector_free(ts->obj);
+		CHECK(mf->n_bits == af->n_bits, EINVAL);
 	}
 
-	free(p->table_state);
+	return 0;
+}
+
+int
+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
+			      const char *name,
+			      struct rte_swx_pipeline_learner_params *params,
+			      uint32_t size,
+			      uint32_t timeout)
+{
+	struct learner *l = NULL;
+	struct action *default_action;
+	struct header *header = NULL;
+	uint32_t action_data_size_max = 0, i;
+	int status = 0;
+
+	CHECK(p, EINVAL);
+
+	CHECK_NAME(name, EINVAL);
+	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
+	CHECK(!learner_find(p, name), EEXIST);
+
+	CHECK(params, EINVAL);
+
+	/* Match checks. */
+	status = learner_match_fields_check(p, params, &header);
+	if (status)
+		return status;
+
+	/* Action checks. */
+	CHECK(params->n_actions, EINVAL);
+
+	CHECK(params->action_names, EINVAL);
+	for (i = 0; i < params->n_actions; i++) {
+		const char *action_name = params->action_names[i];
+		const char *action_field_name = params->action_field_names[i];
+		struct action *a;
+		uint32_t action_data_size;
+
+		CHECK_NAME(action_name, EINVAL);
+
+		a = action_find(p, action_name);
+		CHECK(a, EINVAL);
+
+		status = learner_action_args_check(p, a, action_field_name);
+		if (status)
+			return status;
+
+		action_data_size = a->st ? a->st->n_bits / 8 : 0;
+		if (action_data_size > action_data_size_max)
+			action_data_size_max = action_data_size;
+	}
+
+	CHECK_NAME(params->default_action_name, EINVAL);
+	for (i = 0; i < p->n_actions; i++)
+		if (!strcmp(params->action_names[i],
+			    params->default_action_name))
+			break;
+	CHECK(i < params->n_actions, EINVAL);
+
+	default_action = action_find(p, params->default_action_name);
+	CHECK((default_action->st && params->default_action_data) ||
+	      !params->default_action_data, EINVAL);
+
+	/* Any other checks. */
+	CHECK(size, EINVAL);
+	CHECK(timeout, EINVAL);
+
+	/* Memory allocation. */
+	l = calloc(1, sizeof(struct learner));
+	if (!l)
+		goto nomem;
+
+	l->fields = calloc(params->n_fields, sizeof(struct field *));
+	if (!l->fields)
+		goto nomem;
+
+	l->actions = calloc(params->n_actions, sizeof(struct action *));
+	if (!l->actions)
+		goto nomem;
+
+	l->action_arg = calloc(params->n_actions, sizeof(struct field *));
+	if (!l->action_arg)
+		goto nomem;
+
+	if (action_data_size_max) {
+		l->default_action_data = calloc(1, action_data_size_max);
+		if (!l->default_action_data)
+			goto nomem;
+	}
+
+	/* Node initialization. */
+	strcpy(l->name, name);
+
+	for (i = 0; i < params->n_fields; i++) {
+		const char *field_name = params->field_names[i];
+
+		l->fields[i] = header ?
+			header_field_parse(p, field_name, NULL) :
+			metadata_field_parse(p, field_name);
+	}
+
+	l->n_fields = params->n_fields;
+
+	l->header = header;
+
+	for (i = 0; i < params->n_actions; i++) {
+		const char *mf_name = params->action_field_names[i];
+
+		l->actions[i] = action_find(p, params->action_names[i]);
+
+		l->action_arg[i] = mf_name ? metadata_field_parse(p, mf_name) : NULL;
+	}
+
+	l->default_action = default_action;
+
+	if (default_action->st)
+		memcpy(l->default_action_data,
+		       params->default_action_data,
+		       default_action->st->n_bits / 8);
+
+	l->n_actions = params->n_actions;
+
+	l->default_action_is_const = params->default_action_is_const;
+
+	l->action_data_size_max = action_data_size_max;
+
+	l->size = size;
+
+	l->timeout = timeout;
+
+	l->id = p->n_learners;
+
+	/* Node add to tailq. */
+	TAILQ_INSERT_TAIL(&p->learners, l, node);
+	p->n_learners++;
+
+	return 0;
+
+nomem:
+	if (!l)
+		return -ENOMEM;
+
+	free(l->action_arg);
+	free(l->actions);
+	free(l->fields);
+	free(l);
+
+	return -ENOMEM;
+}
+
+static void
+learner_params_free(struct rte_swx_table_learner_params *params)
+{
+	if (!params)
+		return;
+
+	free(params->key_mask0);
+
+	free(params);
+}
+
+static struct rte_swx_table_learner_params *
+learner_params_get(struct learner *l)
+{
+	struct rte_swx_table_learner_params *params = NULL;
+	struct field *first, *last;
+	uint32_t i;
+
+	/* Memory allocation. */
+	params = calloc(1, sizeof(struct rte_swx_table_learner_params));
+	if (!params)
+		goto error;
+
+	/* Find first (smallest offset) and last (biggest offset) match fields. */
+	first = l->fields[0];
+	last = l->fields[0];
+
+	for (i = 0; i < l->n_fields; i++) {
+		struct field *f = l->fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* Key offset and size. */
+	params->key_offset = first->offset / 8;
+	params->key_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* Memory allocation. */
+	params->key_mask0 = calloc(1, params->key_size);
+	if (!params->key_mask0)
+		goto error;
+
+	/* Key mask. */
+	for (i = 0; i < l->n_fields; i++) {
+		struct field *f = l->fields[i];
+		uint32_t start = (f->offset - first->offset) / 8;
+		size_t size = f->n_bits / 8;
+
+		memset(&params->key_mask0[start], 0xFF, size);
+	}
+
+	/* Action data size. */
+	params->action_data_size = l->action_data_size_max;
+
+	/* Maximum number of keys. */
+	params->n_keys_max = l->size;
+
+	/* Timeout. */
+	params->key_timeout = l->timeout;
+
+	return params;
+
+error:
+	learner_params_free(params);
+	return NULL;
+}
+
+static void
+learner_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		uint32_t j;
+
+		if (!t->learners)
+			continue;
+
+		for (j = 0; j < p->n_learners; j++) {
+			struct learner_runtime *r = &t->learners[j];
+
+			free(r->mailbox);
+			free(r->action_data);
+		}
+
+		free(t->learners);
+		t->learners = NULL;
+	}
+
+	if (p->learner_stats) {
+		for (i = 0; i < p->n_learners; i++)
+			free(p->learner_stats[i].n_pkts_action);
+
+		free(p->learner_stats);
+	}
+}
+
+static int
+learner_build(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+	int status = 0;
+
+	/* Per pipeline: learner statistics. */
+	p->learner_stats = calloc(p->n_learners, sizeof(struct learner_statistics));
+	CHECK(p->learner_stats, ENOMEM);
+
+	for (i = 0; i < p->n_learners; i++) {
+		p->learner_stats[i].n_pkts_action = calloc(p->n_actions, sizeof(uint64_t));
+		CHECK(p->learner_stats[i].n_pkts_action, ENOMEM);
+	}
+
+	/* Per thread: learner run-time. */
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		struct learner *l;
+
+		t->learners = calloc(p->n_learners, sizeof(struct learner_runtime));
+		if (!t->learners) {
+			status = -ENOMEM;
+			goto error;
+		}
+
+		TAILQ_FOREACH(l, &p->learners, node) {
+			struct learner_runtime *r = &t->learners[l->id];
+			uint64_t size;
+			uint32_t j;
+
+			/* r->mailbox. */
+			size = rte_swx_table_learner_mailbox_size_get();
+			if (size) {
+				r->mailbox = calloc(1, size);
+				if (!r->mailbox) {
+					status = -ENOMEM;
+					goto error;
+				}
+			}
+
+			/* r->key. */
+			r->key = l->header ?
+				&t->structs[l->header->struct_id] :
+				&t->structs[p->metadata_struct_id];
+
+			/* r->action_data. */
+			r->action_data = calloc(p->n_actions, sizeof(uint8_t *));
+			if (!r->action_data) {
+				status = -ENOMEM;
+				goto error;
+			}
+
+			for (j = 0; j < l->n_actions; j++) {
+				struct action *a = l->actions[j];
+				struct field *mf = l->action_arg[j];
+				uint8_t *m = t->structs[p->metadata_struct_id];
+
+				r->action_data[a->id] = mf ? &m[mf->offset / 8] : NULL;
+			}
+		}
+	}
+
+	return 0;
+
+error:
+	learner_build_free(p);
+	return status;
+}
+
+static void
+learner_free(struct rte_swx_pipeline *p)
+{
+	learner_build_free(p);
+
+	/* Learner tables. */
+	for ( ; ; ) {
+		struct learner *l;
+
+		l = TAILQ_FIRST(&p->learners);
+		if (!l)
+			break;
+
+		TAILQ_REMOVE(&p->learners, l, node);
+		free(l->fields);
+		free(l->actions);
+		free(l->action_arg);
+		free(l->default_action_data);
+		free(l);
+	}
+}
+
+/*
+ * Table state.
+ */
+static int
+table_state_build(struct rte_swx_pipeline *p)
+{
+	struct table *table;
+	struct selector *s;
+	struct learner *l;
+
+	p->table_state = calloc(p->n_tables + p->n_selectors,
+				sizeof(struct rte_swx_table_state));
+	CHECK(p->table_state, ENOMEM);
+
+	TAILQ_FOREACH(table, &p->tables, node) {
+		struct rte_swx_table_state *ts = &p->table_state[table->id];
+
+		if (table->type) {
+			struct rte_swx_table_params *params;
+
+			/* ts->obj. */
+			params = table_params_get(table);
+			CHECK(params, ENOMEM);
+
+			ts->obj = table->type->ops.create(params,
+				NULL,
+				table->args,
+				p->numa_node);
+
+			table_params_free(params);
+			CHECK(ts->obj, ENODEV);
+		}
+
+		/* ts->default_action_data. */
+		if (table->action_data_size_max) {
+			ts->default_action_data =
+				malloc(table->action_data_size_max);
+			CHECK(ts->default_action_data, ENOMEM);
+
+			memcpy(ts->default_action_data,
+			       table->default_action_data,
+			       table->action_data_size_max);
+		}
+
+		/* ts->default_action_id. */
+		ts->default_action_id = table->default_action->id;
+	}
+
+	TAILQ_FOREACH(s, &p->selectors, node) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
+		struct rte_swx_table_selector_params *params;
+
+		/* ts->obj. */
+		params = selector_table_params_get(s);
+		CHECK(params, ENOMEM);
+
+		ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+
+		selector_params_free(params);
+		CHECK(ts->obj, ENODEV);
+	}
+
+	TAILQ_FOREACH(l, &p->learners, node) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables +
+			p->n_selectors + l->id];
+		struct rte_swx_table_learner_params *params;
+
+		/* ts->obj. */
+		params = learner_params_get(l);
+		CHECK(params, ENOMEM);
+
+		ts->obj = rte_swx_table_learner_create(params, p->numa_node);
+		learner_params_free(params);
+		CHECK(ts->obj, ENODEV);
+
+		/* ts->default_action_data. */
+		if (l->action_data_size_max) {
+			ts->default_action_data = malloc(l->action_data_size_max);
+			CHECK(ts->default_action_data, ENOMEM);
+
+			memcpy(ts->default_action_data,
+			       l->default_action_data,
+			       l->action_data_size_max);
+		}
+
+		/* ts->default_action_id. */
+		ts->default_action_id = l->default_action->id;
+	}
+
+	return 0;
+}
+
+static void
+table_state_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	if (!p->table_state)
+		return;
+
+	for (i = 0; i < p->n_tables; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[i];
+		struct table *table = table_find_by_id(p, i);
+
+		/* ts->obj. */
+		if (table->type && ts->obj)
+			table->type->ops.free(ts->obj);
+
+		/* ts->default_action_data. */
+		free(ts->default_action_data);
+	}
+
+	for (i = 0; i < p->n_selectors; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];
+
+		/* ts->obj. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
+	for (i = 0; i < p->n_learners; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + p->n_selectors + i];
+
+		/* ts->obj. */
+		if (ts->obj)
+			rte_swx_table_learner_free(ts->obj);
+
+		/* ts->default_action_data. */
+		free(ts->default_action_data);
+	}
+
+	free(p->table_state);
 	p->table_state = NULL;
 }
 
@@ -10653,6 +11398,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)
 	TAILQ_INIT(&pipeline->table_types);
 	TAILQ_INIT(&pipeline->tables);
 	TAILQ_INIT(&pipeline->selectors);
+	TAILQ_INIT(&pipeline->learners);
 	TAILQ_INIT(&pipeline->regarrays);
 	TAILQ_INIT(&pipeline->meter_profiles);
 	TAILQ_INIT(&pipeline->metarrays);
@@ -10675,6 +11421,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)
 	metarray_free(p);
 	regarray_free(p);
 	table_state_free(p);
+	learner_free(p);
 	selector_free(p);
 	table_free(p);
 	action_free(p);
@@ -10759,6 +11506,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	if (status)
 		goto error;
 
+	status = learner_build(p);
+	if (status)
+		goto error;
+
 	status = table_state_build(p);
 	if (status)
 		goto error;
@@ -10778,6 +11529,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	metarray_build_free(p);
 	regarray_build_free(p);
 	table_state_build_free(p);
+	learner_build_free(p);
 	selector_build_free(p);
 	table_build_free(p);
 	action_build_free(p);
@@ -10839,6 +11591,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,
 	pipeline->n_actions = n_actions;
 	pipeline->n_tables = n_tables;
 	pipeline->n_selectors = p->n_selectors;
+	pipeline->n_learners = p->n_learners;
 	pipeline->n_regarrays = p->n_regarrays;
 	pipeline->n_metarrays = p->n_metarrays;
 
@@ -11084,6 +11837,75 @@ rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,
+			     uint32_t learner_id,
+			     struct rte_swx_ctl_learner_info *learner)
+{
+	struct learner *l = NULL;
+
+	if (!p || !learner)
+		return -EINVAL;
+
+	l = learner_find_by_id(p, learner_id);
+	if (!l)
+		return -EINVAL;
+
+	strcpy(learner->name, l->name);
+
+	learner->n_match_fields = l->n_fields;
+	learner->n_actions = l->n_actions;
+	learner->default_action_is_const = l->default_action_is_const;
+	learner->size = l->size;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t match_field_id,
+					 struct rte_swx_ctl_table_match_field_info *match_field)
+{
+	struct learner *l;
+	struct field *f;
+
+	if (!p || (learner_id >= p->n_learners) || !match_field)
+		return -EINVAL;
+
+	l = learner_find_by_id(p, learner_id);
+	if (!l || (match_field_id >= l->n_fields))
+		return -EINVAL;
+
+	f = l->fields[match_field_id];
+	match_field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	match_field->is_header = l->header ? 1 : 0;
+	match_field->n_bits = f->n_bits;
+	match_field->offset = f->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
+				    uint32_t learner_id,
+				    uint32_t learner_action_id,
+				    struct rte_swx_ctl_table_action_info *learner_action)
+{
+	struct learner *l;
+
+	if (!p || (learner_id >= p->n_learners) || !learner_action)
+		return -EINVAL;
+
+	l = learner_find_by_id(p, learner_id);
+	if (!l || (learner_action_id >= l->n_actions))
+		return -EINVAL;
+
+	learner_action->action_id = l->actions[learner_action_id]->id;
+
+	return 0;
+}
+
 int
 rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
 				 struct rte_swx_table_state **table_state)
@@ -11188,6 +12010,38 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
+					const char *learner_name,
+					struct rte_swx_learner_stats *stats)
+{
+	struct learner *l;
+	struct learner_statistics *learner_stats;
+
+	if (!p || !learner_name || !learner_name[0] || !stats || !stats->n_pkts_action)
+		return -EINVAL;
+
+	l = learner_find(p, learner_name);
+	if (!l)
+		return -EINVAL;
+
+	learner_stats = &p->learner_stats[l->id];
+
+	memcpy(&stats->n_pkts_action,
+	       &learner_stats->n_pkts_action,
+	       p->n_actions * sizeof(uint64_t));
+
+	stats->n_pkts_hit = learner_stats->n_pkts_hit[1];
+	stats->n_pkts_miss = learner_stats->n_pkts_hit[0];
+
+	stats->n_pkts_learn_ok = learner_stats->n_pkts_learn[0];
+	stats->n_pkts_learn_err = learner_stats->n_pkts_learn[1];
+
+	stats->n_pkts_forget = learner_stats->n_pkts_forget;
+
+	return 0;
+}
+
 int
 rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,
 			      uint32_t regarray_id,
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index 5afca2bc20..2f18a820b9 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -676,6 +676,83 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
 				 const char *name,
 				 struct rte_swx_pipeline_selector_params *params);
 
+/** Pipeline learner table parameters. */
+struct rte_swx_pipeline_learner_params {
+	/** The set of match fields for the current table.
+	 * Restriction: All the match fields of the current table need to be
+	 * part of the same struct, i.e. either all the match fields are part of
+	 * the same header or all the match fields are part of the meta-data.
+	 */
+	const char **field_names;
+
+	/** The number of match fields for the current table. Must be non-zero.
+	 */
+	uint32_t n_fields;
+
+	/** The set of actions for the current table. */
+	const char **action_names;
+
+	/** The number of actions for the current table. Must be at least one.
+	 */
+	uint32_t n_actions;
+
+	/** This table type allows adding the latest lookup key (typically done
+	 * only in the case of lookup miss) to the table with a given action.
+	 * The action arguments are picked up from the packet meta-data: for
+	 * each action, a set of successive meta-data fields (with the name of
+	 * the first such field provided here) is 1:1 mapped to the action
+	 * arguments. These meta-data fields must be set with the actual values
+	 * of the action arguments before the key add operation.
+	 */
+	const char **action_field_names;
+
+	/** The default table action that gets executed on lookup miss. Must be
+	 * one of the table actions included in the *action_names*.
+	 */
+	const char *default_action_name;
+
+	/** Default action data. The size of this array is the action data size
+	 * of the default action. Must be NULL if the default action data size
+	 * is zero.
+	 */
+	uint8_t *default_action_data;
+
+	/** If non-zero (true), then the default action of the current table
+	 * cannot be changed. If zero (false), then the default action can be
+	 * changed in the future with another action from the *action_names*
+	 * list.
+	 */
+	int default_action_is_const;
+};
+
+/**
+ * Pipeline learner table configure
+ *
+ * @param[out] p
+ *   Pipeline handle.
+ * @param[in] name
+ *   Learner table name.
+ * @param[in] params
+ *   Learner table parameters.
+ * @param[in] size
+ *   The maximum number of table entries. Must be non-zero.
+ * @param[in] timeout
+ *   Table entry timeout in seconds. Must be non-zero.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough space/cannot allocate memory;
+ *   -EEXIST: Learner table with this name already exists;
+ *   -ENODEV: Learner table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
+				const char *name,
+				struct rte_swx_pipeline_learner_params *params,
+				uint32_t size,
+				uint32_t timeout);
+
 /**
  * Pipeline register array configure
  *
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index c57893f18c..d9cd1d0595 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -20,7 +20,10 @@
 #define TABLE_ACTIONS_BLOCK 4
 #define SELECTOR_BLOCK 5
 #define SELECTOR_SELECTOR_BLOCK 6
-#define APPLY_BLOCK 7
+#define LEARNER_BLOCK 7
+#define LEARNER_KEY_BLOCK 8
+#define LEARNER_ACTIONS_BLOCK 9
+#define APPLY_BLOCK 10
 
 /*
  * extobj.
@@ -1281,6 +1284,420 @@ selector_block_parse(struct selector_spec *s,
 	return -EINVAL;
 }
 
+/*
+ * learner.
+ *
+ * learner {
+ *	key {
+ *		MATCH_FIELD_NAME
+ *		...
+ *	}
+ *	actions {
+ *		ACTION_NAME args METADATA_FIELD_NAME
+ *		...
+ *	}
+ *	default_action ACTION_NAME args none | ARGS_BYTE_ARRAY [ const ]
+ *	size SIZE
+ *	timeout TIMEOUT_IN_SECONDS
+ * }
+ */
+struct learner_spec {
+	char *name;
+	struct rte_swx_pipeline_learner_params params;
+	uint32_t size;
+	uint32_t timeout;
+};
+
+static void
+learner_spec_free(struct learner_spec *s)
+{
+	uintptr_t default_action_name;
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	free(s->name);
+	s->name = NULL;
+
+	for (i = 0; i < s->params.n_fields; i++) {
+		uintptr_t name = (uintptr_t)s->params.field_names[i];
+
+		free((void *)name);
+	}
+
+	free(s->params.field_names);
+	s->params.field_names = NULL;
+
+	s->params.n_fields = 0;
+
+	for (i = 0; i < s->params.n_actions; i++) {
+		uintptr_t name = (uintptr_t)s->params.action_names[i];
+
+		free((void *)name);
+	}
+
+	free(s->params.action_names);
+	s->params.action_names = NULL;
+
+	for (i = 0; i < s->params.n_actions; i++) {
+		uintptr_t name = (uintptr_t)s->params.action_field_names[i];
+
+		free((void *)name);
+	}
+
+	free(s->params.action_field_names);
+	s->params.action_field_names = NULL;
+
+	s->params.n_actions = 0;
+
+	default_action_name = (uintptr_t)s->params.default_action_name;
+	free((void *)default_action_name);
+	s->params.default_action_name = NULL;
+
+	free(s->params.default_action_data);
+	s->params.default_action_data = NULL;
+
+	s->params.default_action_is_const = 0;
+
+	s->size = 0;
+
+	s->timeout = 0;
+}
+
+static int
+learner_key_statement_parse(uint32_t *block_mask,
+			    char **tokens,
+			    uint32_t n_tokens,
+			    uint32_t n_lines,
+			    uint32_t *err_line,
+			    const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid key statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_KEY_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_key_block_parse(struct learner_spec *s,
+			uint32_t *block_mask,
+			char **tokens,
+			uint32_t n_tokens,
+			uint32_t n_lines,
+			uint32_t *err_line,
+			const char **err_msg)
+{
+	const char **new_field_names = NULL;
+	char *field_name = NULL;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_KEY_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid match field statement.";
+		return -EINVAL;
+	}
+
+	field_name = strdup(tokens[0]);
+	new_field_names = realloc(s->params.field_names, (s->params.n_fields + 1) * sizeof(char *));
+	if (!field_name || !new_field_names) {
+		free(field_name);
+		free(new_field_names);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.field_names = new_field_names;
+	s->params.field_names[s->params.n_fields] = field_name;
+	s->params.n_fields++;
+
+	return 0;
+}
+
+static int
+learner_actions_statement_parse(uint32_t *block_mask,
+				char **tokens,
+				uint32_t n_tokens,
+				uint32_t n_lines,
+				uint32_t *err_line,
+				const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid actions statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_ACTIONS_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_actions_block_parse(struct learner_spec *s,
+			    uint32_t *block_mask,
+			    char **tokens,
+			    uint32_t n_tokens,
+			    uint32_t n_lines,
+			    uint32_t *err_line,
+			    const char **err_msg)
+{
+	const char **new_action_names = NULL;
+	const char **new_action_field_names = NULL;
+	char *action_name = NULL, *action_field_name = NULL;
+	int has_args = 1;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_ACTIONS_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if ((n_tokens != 3) || strcmp(tokens[1], "args")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid action name statement.";
+		return -EINVAL;
+	}
+
+	if (!strcmp(tokens[2], "none"))
+		has_args = 0;
+
+	action_name = strdup(tokens[0]);
+
+	if (has_args)
+		action_field_name = strdup(tokens[2]);
+
+	new_action_names = realloc(s->params.action_names,
+				   (s->params.n_actions + 1) * sizeof(char *));
+
+	new_action_field_names = realloc(s->params.action_field_names,
+					 (s->params.n_actions + 1) * sizeof(char *));
+
+	if (!action_name ||
+	    (has_args && !action_field_name) ||
+	    !new_action_names ||
+	    !new_action_field_names) {
+		free(action_name);
+		free(action_field_name);
+		free(new_action_names);
+		free(new_action_field_names);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.action_names = new_action_names;
+	s->params.action_names[s->params.n_actions] = action_name;
+	s->params.action_field_names = new_action_field_names;
+	s->params.action_field_names[s->params.n_actions] = action_field_name;
+	s->params.n_actions++;
+
+	return 0;
+}
+
+static int
+learner_statement_parse(struct learner_spec *s,
+		      uint32_t *block_mask,
+		      char **tokens,
+		      uint32_t n_tokens,
+		      uint32_t n_lines,
+		      uint32_t *err_line,
+		      const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 3) || strcmp(tokens[2], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid learner statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	if (!s->name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_block_parse(struct learner_spec *s,
+		    uint32_t *block_mask,
+		    char **tokens,
+		    uint32_t n_tokens,
+		    uint32_t n_lines,
+		    uint32_t *err_line,
+		    const char **err_msg)
+{
+	if (*block_mask & (1 << LEARNER_KEY_BLOCK))
+		return learner_key_block_parse(s,
+					       block_mask,
+					       tokens,
+					       n_tokens,
+					       n_lines,
+					       err_line,
+					       err_msg);
+
+	if (*block_mask & (1 << LEARNER_ACTIONS_BLOCK))
+		return learner_actions_block_parse(s,
+						   block_mask,
+						   tokens,
+						   n_tokens,
+						   n_lines,
+						   err_line,
+						   err_msg);
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_BLOCK);
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "key"))
+		return learner_key_statement_parse(block_mask,
+						   tokens,
+						   n_tokens,
+						   n_lines,
+						   err_line,
+						   err_msg);
+
+	if (!strcmp(tokens[0], "actions"))
+		return learner_actions_statement_parse(block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
+
+	if (!strcmp(tokens[0], "default_action")) {
+		if (((n_tokens != 4) && (n_tokens != 5)) ||
+		    strcmp(tokens[2], "args") ||
+		    strcmp(tokens[3], "none") ||
+		    ((n_tokens == 5) && strcmp(tokens[4], "const"))) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid default_action statement.";
+			return -EINVAL;
+		}
+
+		if (s->params.default_action_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Duplicate default_action stmt.";
+			return -EINVAL;
+		}
+
+		s->params.default_action_name = strdup(tokens[1]);
+		if (!s->params.default_action_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		if (n_tokens == 5)
+			s->params.default_action_is_const = 1;
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "size")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid size statement.";
+			return -EINVAL;
+		}
+
+		s->size = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid size argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "timeout")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid timeout statement.";
+			return -EINVAL;
+		}
+
+		s->timeout = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid timeout argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* Anything else. */
+	if (err_line)
+		*err_line = n_lines;
+	if (err_msg)
+		*err_msg = "Invalid statement.";
+	return -EINVAL;
+}
+
 /*
  * regarray.
  *
@@ -1545,6 +1962,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	struct action_spec action_spec = {0};
 	struct table_spec table_spec = {0};
 	struct selector_spec selector_spec = {0};
+	struct learner_spec learner_spec = {0};
 	struct regarray_spec regarray_spec = {0};
 	struct metarray_spec metarray_spec = {0};
 	struct apply_spec apply_spec = {0};
@@ -1761,6 +2179,40 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* learner block. */
+		if (block_mask & (1 << LEARNER_BLOCK)) {
+			status = learner_block_parse(&learner_spec,
+						     &block_mask,
+						     tokens,
+						     n_tokens,
+						     n_lines,
+						     err_line,
+						     err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << LEARNER_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_learner_config(p,
+				learner_spec.name,
+				&learner_spec.params,
+				learner_spec.size,
+				learner_spec.timeout);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Learner table configuration error.";
+				goto error;
+			}
+
+			learner_spec_free(&learner_spec);
+
+			continue;
+		}
+
 		/* apply block. */
 		if (block_mask & (1 << APPLY_BLOCK)) {
 			status = apply_block_parse(&apply_spec,
@@ -1934,6 +2386,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* learner. */
+		if (!strcmp(tokens[0], "learner")) {
+			status = learner_statement_parse(&learner_spec,
+							 &block_mask,
+							 tokens,
+							 n_tokens,
+							 n_lines,
+							 err_line,
+							 err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
 		/* regarray. */
 		if (!strcmp(tokens[0], "regarray")) {
 			status = regarray_statement_parse(&regarray_spec,
@@ -2042,6 +2509,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	action_spec_free(&action_spec);
 	table_spec_free(&table_spec);
 	selector_spec_free(&selector_spec);
+	learner_spec_free(&learner_spec);
 	regarray_spec_free(&regarray_spec);
 	metarray_spec_free(&metarray_spec);
 	apply_spec_free(&apply_spec);
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index ff0974c2ee..c92d7d11cb 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -129,4 +129,12 @@ EXPERIMENTAL {
 	rte_swx_ctl_selector_field_info_get;
 	rte_swx_ctl_selector_group_id_field_info_get;
 	rte_swx_ctl_selector_member_id_field_info_get;
+
+	#added in 21.11
+	rte_swx_ctl_pipeline_learner_default_entry_add;
+	rte_swx_ctl_pipeline_learner_stats_read;
+	rte_swx_ctl_learner_action_info_get;
+	rte_swx_ctl_learner_info_get;
+	rte_swx_ctl_learner_match_field_info_get;
+	rte_swx_pipeline_learner_config;
 };
-- 
2.17.1


  reply	other threads:[~2021-08-13 23:52 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
2021-08-13 23:52 ` Cristian Dumitrescu [this message]
2021-08-13 23:52 ` [dpdk-dev] [PATCH 3/4] examples/pipeline: add support for " Cristian Dumitrescu
2021-08-13 23:52 ` [dpdk-dev] [PATCH 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
2021-08-14 13:43 ` [dpdk-dev] [PATCH V2 1/4] table: add support learner tables Cristian Dumitrescu
2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 2/4] pipeline: add support for " Cristian Dumitrescu
2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 3/4] examples/pipeline: " Cristian Dumitrescu
2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
2021-08-14 13:59   ` [dpdk-dev] [PATCH V3 1/4] table: add support learner tables Cristian Dumitrescu
2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 2/4] pipeline: add support for " Cristian Dumitrescu
2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 3/4] examples/pipeline: " Cristian Dumitrescu
2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
2021-08-16 12:22     ` [dpdk-dev] [PATCH V4 1/4] table: add support learner tables Cristian Dumitrescu
2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 2/4] pipeline: add support for " Cristian Dumitrescu
2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 3/4] examples/pipeline: " Cristian Dumitrescu
2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
2021-09-20 15:01       ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Cristian Dumitrescu
2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 2/4] pipeline: add support for " Cristian Dumitrescu
2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 3/4] examples/pipeline: " Cristian Dumitrescu
2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
2021-09-27  7:53         ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Thomas Monjalon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210813235232.65757-2-cristian.dumitrescu@intel.com \
    --to=cristian.dumitrescu@intel.com \
    --cc=dev@dpdk.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.