All of lore.kernel.org
 help / color / mirror / Atom feed
* [dpdk-dev] [PATCH 1/4] table: add support learner tables
@ 2021-08-13 23:52 Cristian Dumitrescu
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 2/4] pipeline: add support for " Cristian Dumitrescu
                   ` (3 more replies)
  0 siblings, 4 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-13 23:52 UTC (permalink / raw)
  To: dev

A learner table is typically used for learning or connection tracking,
where it allows for the implementation of the "add on miss" scenario:
whenever the lookup key is not found in the table (lookup miss), the
data plane can decide to add this key to the table with a given action
with no control plane intervention. Likewise, the table keys expire
based on a configurable timeout and are automatically deleted from the
table with no control plane intervention.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/table/meson.build             |   2 +
 lib/table/rte_swx_table_learner.c | 616 ++++++++++++++++++++++++++++++
 lib/table/rte_swx_table_learner.h | 206 ++++++++++
 lib/table/version.map             |   9 +
 4 files changed, 833 insertions(+)
 create mode 100644 lib/table/rte_swx_table_learner.c
 create mode 100644 lib/table/rte_swx_table_learner.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index a1384456a9..ac1f1aac27 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+        'rte_swx_table_learner.c',
         'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
@@ -21,6 +22,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+        'rte_swx_table_learner.h',
         'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
new file mode 100644
index 0000000000..5255fb0202
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.c
@@ -0,0 +1,616 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_learner.h"
+
+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
+#endif
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+/*
+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
+ */
+static inline uint32_t
+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
+{
+	uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;
+
+	switch (n_bytes) {
+	case 8: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint32_t result = 1;
+
+		if (xor0)
+			result = 0;
+		return result;
+	}
+
+	case 16: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t or = xor0 | xor1;
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 32: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t or = (xor0 | xor1) | (xor2 | xor3);
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 64: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);
+		uint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);
+		uint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);
+		uint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);
+		uint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |
+			      ((xor4 | xor5) | (xor6 | xor7));
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	default: {
+		uint32_t i;
+
+		for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+			if (a64[i] != (b64[i] & b_mask64[i]))
+				return 0;
+		return 1;
+	}
+	}
+}
+
+#define TABLE_KEYS_PER_BUCKET 4
+
+#define TABLE_BUCKET_PAD_SIZE \
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+
+struct table_bucket {
+	uint32_t time[TABLE_KEYS_PER_BUCKET];
+	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
+	uint8_t key[0];
+};
+
+struct table_params {
+	/* The real key size. Must be non-zero. */
+	size_t key_size;
+
+	/* They key size upgrated to the next power of 2. This used for hash generation (in
+	 * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why
+	 * key sizes bigger than 64 bytes are not allowed.
+	 */
+	size_t key_size_pow2;
+
+	/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
+	size_t key_size_log2;
+
+	/* The key offset within the key buffer. */
+	size_t key_offset;
+
+	/* The real action data size. */
+	size_t action_data_size;
+
+	/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
+	 * next power of 2.
+	 */
+	size_t data_size_pow2;
+
+	/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t data_size_log2;
+
+	/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
+	size_t n_buckets;
+
+	/* Bucket mask. Purpose: replace modulo with bitmask and operation. */
+	size_t bucket_mask;
+
+	/* Total number of key bytes in the bucket, including the key padding bytes. There are
+	 * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
+	 */
+	size_t bucket_key_all_size;
+
+	/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
+	size_t bucket_size;
+
+	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t bucket_size_log2;
+
+	/* Timeout in CPU clock cycles. */
+	uint64_t key_timeout;
+
+	/* Total memory size. */
+	size_t total_size;
+};
+
+struct table {
+	/* Table parameters. */
+	struct table_params params;
+
+	/* Key mask. Array of *key_size* bytes. */
+	uint8_t key_mask0[RTE_CACHE_LINE_SIZE];
+
+	/* Table buckets. */
+	uint8_t buckets[0];
+} __rte_cache_aligned;
+
+static int
+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
+{
+	/* Check input parameters. */
+	if (!params ||
+	    !params->key_size ||
+	    (params->key_size > 64) ||
+	    !params->n_keys_max ||
+	    (params->n_keys_max > 1U << 31) ||
+	    !params->key_timeout)
+		return -EINVAL;
+
+	/* Key. */
+	p->key_size = params->key_size;
+
+	p->key_size_pow2 = rte_align64pow2(p->key_size);
+	if (p->key_size_pow2 < 8)
+		p->key_size_pow2 = 8;
+
+	p->key_size_log2 = __builtin_ctzll(p->key_size_pow2);
+
+	p->key_offset = params->key_offset;
+
+	/* Data. */
+	p->action_data_size = params->action_data_size;
+
+	p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
+
+	p->data_size_log2 = __builtin_ctzll(p->data_size_pow2);
+
+	/* Buckets. */
+	p->n_buckets = rte_align32pow2(params->n_keys_max);
+
+	p->bucket_mask = p->n_buckets - 1;
+
+	p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
+
+	p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
+					 p->bucket_key_all_size +
+					 TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
+
+	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
+
+	/* Timeout. */
+	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+
+	/* Total size. */
+	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
+
+	return 0;
+}
+
+static inline struct table_bucket *
+table_bucket_get(struct table *t, size_t bucket_id)
+{
+	return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
+}
+
+static inline uint8_t *
+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return &b->key[bucket_key_pos << t->params.key_size_log2];
+}
+
+static inline uint64_t *
+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return (uint64_t *)&b->key[t->params.bucket_key_all_size +
+				   (bucket_key_pos << t->params.data_size_log2)];
+}
+
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
+{
+	struct table_params p;
+	int status;
+
+	status = table_params_get(&p, params);
+
+	return status ? 0 : p.total_size;
+}
+
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
+{
+	struct table_params p;
+	struct table *t;
+	int status;
+
+	/* Check and process the input parameters. */
+	status = table_params_get(&p, params);
+	if (status)
+		return NULL;
+
+	/* Memory allocation. */
+	t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t)
+		return NULL;
+
+	/* Memory initialization. */
+	memcpy(&t->params, &p, sizeof(struct table_params));
+
+	if (params->key_mask0)
+		memcpy(t->key_mask0, params->key_mask0, params->key_size);
+	else
+		memset(t->key_mask0, 0xFF, params->key_size);
+
+	return t;
+}
+
+void
+rte_swx_table_learner_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	env_free(t, t->params.total_size);
+}
+
+struct mailbox {
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	struct table_bucket *bucket;
+
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	uint32_t input_sig;
+
+	/* Writer: lookup state 1. Reader(s): add(). */
+	uint8_t *input_key;
+
+	/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
+	uint32_t hit;
+
+	/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
+	size_t bucket_key_pos;
+
+	/* State. */
+	int state;
+};
+
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t input_time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+
+	switch (m->state) {
+	case 0: {
+		uint8_t *input_key;
+		struct table_bucket *b;
+		size_t bucket_id;
+		uint32_t input_sig;
+
+		input_key = &(*key)[t->params.key_offset];
+		input_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);
+		bucket_id = input_sig & t->params.bucket_mask;
+		b = table_bucket_get(t, bucket_id);
+
+		rte_prefetch0(b);
+		rte_prefetch0(&b->key[0]);
+		rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
+
+		m->bucket = b;
+		m->input_key = input_key;
+		m->input_sig = input_sig | 1;
+		m->state = 1;
+		return 0;
+	}
+
+	case 1: {
+		struct table_bucket *b = m->bucket;
+		uint32_t i;
+
+		/* Search the input key through the bucket keys. */
+		for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+			uint64_t time = b->time[i];
+			uint32_t sig = b->sig[i];
+			uint8_t *key = table_bucket_key_get(t, b, i);
+
+			time <<= 32;
+
+			if ((time > input_time) &&
+			    (sig == m->input_sig) &&
+			    table_keycmp(key, m->input_key, t->key_mask0, t->params.key_size_pow2)) {
+				uint64_t *data = table_bucket_data_get(t, b, i);
+
+				/* Hit. */
+				rte_prefetch0(data);
+
+				b->time[i] = (input_time + t->params.key_timeout) >> 32;
+
+				m->hit = 1;
+				m->bucket_key_pos = i;
+				m->state = 0;
+
+				*action_id = data[0];
+				*action_data = (uint8_t *)&data[1];
+				*hit = 1;
+				return 1;
+			}
+		}
+
+		/* Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+
+	default:
+		/* This state should never be reached. Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+}
+
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t input_time,
+			  uint64_t action_id,
+			  uint8_t *action_data)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b = m->bucket;
+	uint32_t i;
+
+	/* Lookup hit: The key, key signature and key time are already properly configured (the key
+	 * time was bumped by lookup), only the key data need to be updated.
+	 */
+	if (m->hit) {
+		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+
+		/* Install the key data. */
+		data[0] = action_id;
+		if (t->params.action_data_size && action_data)
+			memcpy(&data[1], action_data, t->params.action_data_size);
+
+		return 0;
+	}
+
+	/* Lookup miss: Search for a free position in the current bucket and install the key. */
+	for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+		uint64_t time = b->time[i];
+
+		time <<= 32;
+
+		/* Free position: Either there was never a key installed here, so the key time is
+		 * set to zero (the init value), which is always less than the current time, or this
+		 * position was used before, but the key expired (the key time is in the past).
+		 */
+		if (time < input_time) {
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint64_t *data = table_bucket_data_get(t, b, i);
+
+			/* Install the key. */
+			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			b->sig[i] = m->input_sig;
+			memcpy(key, m->input_key, t->params.key_size);
+
+			/* Install the key data. */
+			data[0] = action_id;
+			if (t->params.action_data_size && action_data)
+				memcpy(&data[1], action_data, t->params.action_data_size);
+
+			/* Mailbox. */
+			m->hit = 1;
+			m->bucket_key_pos = i;
+
+			return 0;
+		}
+	}
+
+	/* Bucket full. */
+	return 1;
+}
+
+void
+rte_swx_table_learner_delete(void *table __rte_unused,
+			     void *mailbox)
+{
+	struct mailbox *m = mailbox;
+
+	if (m->hit) {
+		struct table_bucket *b = m->bucket;
+
+		/* Expire the key. */
+		b->time[m->bucket_key_pos] = 0;
+
+		/* Mailbox. */
+		m->hit = 0;
+	}
+}
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
new file mode 100644
index 0000000000..d6ec733655
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Learner Table
+ *
+ * The learner table API.
+ *
+ * This table type is typically used for learning or connection tracking, where it allows for the
+ * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no
+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
+ * are automatically deleted from the table with no control plane intervention.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+/** Learner table creation parameters. */
+struct rte_swx_table_learner_params {
+	/** Key size in bytes. Must be non-zero. */
+	uint32_t key_size;
+
+	/** Offset of the first byte of the key within the key buffer. */
+	uint32_t key_offset;
+
+	/** Mask of *key_size* bytes logically laid over the bytes at positions
+	 * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify
+	 * which bits from the key buffer are part of the key and which ones are not. A bit value of
+	 * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while
+	 * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the
+	 * key, i.e. the *key_mask0* is an all-ones mask.
+	 */
+	uint8_t *key_mask0;
+
+	/** Maximum size (in bytes) of the action data. The data stored in the table for each entry
+	 * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.
+	 */
+	uint32_t action_data_size;
+
+	/** Maximum number of keys to be stored in the table together with their associated data. */
+	uint32_t n_keys_max;
+
+	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
+	 * deleted from the table after this many seconds.
+	 */
+	uint32_t key_timeout;
+};
+
+/**
+ * Learner table memory footprint get
+ *
+ * @param[in] params
+ *   Table create parameters.
+ * @return
+ *   Table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);
+
+/**
+ * Learner table mailbox size get
+ *
+ * The mailbox is used to store the context of a lookup operation that is in
+ * progress and it is passed as a parameter to the lookup operation. This allows
+ * for multiple concurrent lookup operations into the same table.
+ *
+ * @return
+ *   Table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void);
+
+/**
+ * Learner table create
+ *
+ * @param[in] params
+ *   Table creation parameters.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
+
+/**
+ * Learner table key lookup
+ *
+ * The table lookup operation searches a given key in the table and upon its completion it returns
+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case
+ * of lookup hit, the action_id and the action_data associated with the key are also returned.
+ *
+ * Multiple invocations of this function may be required in order to complete a single table lookup
+ * operation for a given table and a given lookup key. The completion of the table lookup operation
+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked
+ * again with exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and
+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent
+ * table key lookup and add operations into the same table.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current table lookup operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key
+ *   Lookup key. Its size must be equal to the table *key_size*.
+ * @param[out] action_id
+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid
+ *   when the function returns 1 and *hit* is set to true.
+ * @param[out] action_data
+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*
+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.
+ * @param[out] hit
+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero
+ *   (false) on table lookup miss.
+ * @return
+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation
+ *   is completed. No other return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit);
+
+/**
+ * Learner table key add
+ *
+ * This operation takes the latest key that was looked up in the table and adds it to the table with
+ * the given action ID and action data. Typically, this operation is only invoked when the latest
+ * lookup operation in the current table resulted in lookup miss.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[out] action_id
+ *   ID of the action associated with the key.
+ * @param[out] action_data
+ *   Action data for the *action_id* action.
+ * @return
+ *   0 on success, 1 or error (table full).
+ */
+__rte_experimental
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t time,
+			  uint64_t action_id,
+			  uint8_t *action_data);
+
+/**
+ * Learner table key delete
+ *
+ * This operation takes the latest key that was looked up in the table and deletes it from the
+ * table. Typically, this operation is only invoked to force the deletion of the key before the key
+ * expires on timeout due to inactivity.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_delete(void *table,
+			     void *mailbox);
+
+/**
+ * Learner table free
+ *
+ * @param[in] table
+ *   Table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index 29301480cb..f973a36ecc 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -36,4 +36,13 @@ EXPERIMENTAL {
 	rte_swx_table_selector_group_set;
 	rte_swx_table_selector_mailbox_size_get;
 	rte_swx_table_selector_select;
+
+	# added in 21.11
+	rte_swx_table_learner_add;
+	rte_swx_table_learner_create;
+	rte_swx_table_learner_delete;
+	rte_swx_table_learner_footprint_get;
+	rte_swx_table_learner_free;
+	rte_swx_table_learner_lookup;
+	rte_swx_table_learner_mailbox_size_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH 2/4] pipeline: add support for learner tables
  2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
@ 2021-08-13 23:52 ` Cristian Dumitrescu
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 3/4] examples/pipeline: " Cristian Dumitrescu
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-13 23:52 UTC (permalink / raw)
  To: dev

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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH 3/4] examples/pipeline: add support for learner tables
  2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 2/4] pipeline: add support for " Cristian Dumitrescu
@ 2021-08-13 23:52 ` 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
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-13 23:52 UTC (permalink / raw)
  To: dev

Add application-level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 174 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index a29be05ef4..ad6e3db8d7 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1829,6 +1829,104 @@ cmd_pipeline_selector_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
 }
 
+static int
+pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				   const char *learner_name,
+				   FILE *file,
+				   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_learner_default_entry_read(p,
+									learner_name,
+									line,
+									&is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_default_entry_add(p,
+									learner_name,
+									entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_learner_default_help[] =
+"pipeline <pipeline_name> learner <learner_name> default <file_name>\n";
+
+static void
+cmd_pipeline_learner_default(char **tokens,
+			     uint32_t n_tokens,
+			     char *out,
+			     size_t out_size,
+			     void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *learner_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	learner_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_learner_default_entry_add(p->ctl,
+						    learner_name,
+						    file,
+						    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2503,6 +2601,64 @@ cmd_pipeline_stats(char **tokens,
 			out += strlen(out);
 		}
 	}
+
+	snprintf(out, out_size, "\nLearner tables:\n");
+	out_size -= strlen(out);
+	out += strlen(out);
+
+	for (i = 0; i < info.n_learners; i++) {
+		struct rte_swx_ctl_learner_info learner_info;
+		uint64_t n_pkts_action[info.n_actions];
+		struct rte_swx_learner_stats stats = {
+			.n_pkts_hit = 0,
+			.n_pkts_miss = 0,
+			.n_pkts_action = n_pkts_action,
+		};
+		uint32_t j;
+
+		status = rte_swx_ctl_learner_info_get(p->p, i, &learner_info);
+		if (status) {
+			snprintf(out, out_size, "Learner table info get error.");
+			return;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_stats_read(p->p, learner_info.name, &stats);
+		if (status) {
+			snprintf(out, out_size, "Learner table stats read error.");
+			return;
+		}
+
+		snprintf(out, out_size, "\tLearner table %s:\n"
+			"\t\tHit (packets): %" PRIu64 "\n"
+			"\t\tMiss (packets): %" PRIu64 "\n"
+			"\t\tLearn OK (packets): %" PRIu64 "\n"
+			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tForget (packets): %" PRIu64 "\n",
+			learner_info.name,
+			stats.n_pkts_hit,
+			stats.n_pkts_miss,
+			stats.n_pkts_learn_ok,
+			stats.n_pkts_learn_err,
+			stats.n_pkts_forget);
+		out_size -= strlen(out);
+		out += strlen(out);
+
+		for (j = 0; j < info.n_actions; j++) {
+			struct rte_swx_ctl_action_info action_info;
+
+			status = rte_swx_ctl_action_info_get(p->p, j, &action_info);
+			if (status) {
+				snprintf(out, out_size, "Action info get error.");
+				return;
+			}
+
+			snprintf(out, out_size, "\t\tAction %s (packets): %" PRIu64 "\n",
+				action_info.name,
+				stats.n_pkts_action[j]);
+			out_size -= strlen(out);
+			out += strlen(out);
+		}
+	}
 }
 
 static const char cmd_thread_pipeline_enable_help[] =
@@ -2634,6 +2790,7 @@ cmd_help(char **tokens,
 			"\tpipeline selector group member add\n"
 			"\tpipeline selector group member delete\n"
 			"\tpipeline selector show\n"
+			"\tpipeline learner default\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2783,6 +2940,15 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "learner") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_learner_default_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -3031,6 +3197,14 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "learner") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_learner_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH 4/4] examples/pipeline: add learner table example
  2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 2/4] pipeline: add support for " Cristian Dumitrescu
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 3/4] examples/pipeline: " Cristian Dumitrescu
@ 2021-08-13 23:52 ` Cristian Dumitrescu
  2021-08-14 13:43 ` [dpdk-dev] [PATCH V2 1/4] table: add support learner tables Cristian Dumitrescu
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-13 23:52 UTC (permalink / raw)
  To: dev

Added the files to illustrate the learner table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/examples/learner.cli  |  35 ++++++++
 examples/pipeline/examples/learner.spec | 109 ++++++++++++++++++++++++
 2 files changed, 144 insertions(+)
 create mode 100644 examples/pipeline/examples/learner.cli
 create mode 100644 examples/pipeline/examples/learner.spec

diff --git a/examples/pipeline/examples/learner.cli b/examples/pipeline/examples/learner.cli
new file mode 100644
index 0000000000..732b6cb85f
--- /dev/null
+++ b/examples/pipeline/examples/learner.cli
@@ -0,0 +1,35 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+;
+; Customize the LINK parameters to match your setup.
+;
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+;
+; PIPELINE0 setup.
+;
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/learner.spec
+
+;
+; Pipelines-to-threads mapping.
+;
+thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
new file mode 100644
index 0000000000..0fa56295a6
--- /dev/null
+++ b/examples/pipeline/examples/learner.spec
@@ -0,0 +1,109 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+
+	// Arguments for the "fwd_action" action.
+	bit<32> fwd_action_arg_port_out
+}
+
+metadata instanceof metadata_t
+
+//
+// Registers.
+//
+regarray counter size 1 initval 0
+
+//
+// Actions
+//
+struct fwd_action_args_t {
+	bit<32> port_out
+}
+
+action fwd_action args instanceof fwd_action_args_t {
+	mov m.port_out t.port_out
+	return
+}
+
+action learn_action args none {
+	// Read current counter value into m.fwd_action_arg_port_out.
+	regrd m.fwd_action_arg_port_out counter 0
+
+	// Increment the counter.
+	regadd counter 0 1
+
+	// Limit the values of m.fwd_action_arg_port_out to 0 .. 3.
+	and m.fwd_action_arg_port_out 3
+
+	// Add the current lookup key to the table. The associated action is fwd_action, with the
+	// action parameters read from the packet meta-data starting with the
+	// m.fwd_action_arg_port_out field.
+	learn fwd_action
+
+	// Send the current packet to the same output port.
+	mov m.port_out m.fwd_action_arg_port_out
+
+	return
+}
+
+//
+// Tables.
+//
+learner fwd_table {
+	key {
+		h.ipv4.dst_addr
+	}
+
+	actions {
+		fwd_action args m.fwd_action_arg_port_out
+		learn_action args none
+	}
+
+	default_action learn_action args none
+	size 1048576
+	timeout 120
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	table fwd_table
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V2 1/4] table: add support learner tables
  2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
                   ` (2 preceding siblings ...)
  2021-08-13 23:52 ` [dpdk-dev] [PATCH 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
@ 2021-08-14 13:43 ` Cristian Dumitrescu
  2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 2/4] pipeline: add support for " Cristian Dumitrescu
                     ` (3 more replies)
  3 siblings, 4 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:43 UTC (permalink / raw)
  To: dev

A learner table is typically used for learning or connection tracking,
where it allows for the implementation of the "add on miss" scenario:
whenever the lookup key is not found in the table (lookup miss), the
data plane can decide to add this key to the table with a given action
with no control plane intervention. Likewise, the table keys expire
based on a configurable timeout and are automatically deleted from the
table with no control plane intervention.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-18023 ("[V2,1/5] pipeline: prepare for variable size headers")

V2: fixed one "line too long" coding style warning.

 lib/table/meson.build             |   2 +
 lib/table/rte_swx_table_learner.c | 617 ++++++++++++++++++++++++++++++
 lib/table/rte_swx_table_learner.h | 206 ++++++++++
 lib/table/version.map             |   9 +
 4 files changed, 834 insertions(+)
 create mode 100644 lib/table/rte_swx_table_learner.c
 create mode 100644 lib/table/rte_swx_table_learner.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index a1384456a9..ac1f1aac27 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+        'rte_swx_table_learner.c',
         'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
@@ -21,6 +22,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+        'rte_swx_table_learner.h',
         'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
new file mode 100644
index 0000000000..c3c840ff06
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.c
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_learner.h"
+
+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
+#endif
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+/*
+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
+ */
+static inline uint32_t
+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
+{
+	uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;
+
+	switch (n_bytes) {
+	case 8: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint32_t result = 1;
+
+		if (xor0)
+			result = 0;
+		return result;
+	}
+
+	case 16: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t or = xor0 | xor1;
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 32: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t or = (xor0 | xor1) | (xor2 | xor3);
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 64: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);
+		uint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);
+		uint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);
+		uint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);
+		uint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |
+			      ((xor4 | xor5) | (xor6 | xor7));
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	default: {
+		uint32_t i;
+
+		for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+			if (a64[i] != (b64[i] & b_mask64[i]))
+				return 0;
+		return 1;
+	}
+	}
+}
+
+#define TABLE_KEYS_PER_BUCKET 4
+
+#define TABLE_BUCKET_PAD_SIZE \
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+
+struct table_bucket {
+	uint32_t time[TABLE_KEYS_PER_BUCKET];
+	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
+	uint8_t key[0];
+};
+
+struct table_params {
+	/* The real key size. Must be non-zero. */
+	size_t key_size;
+
+	/* They key size upgrated to the next power of 2. This used for hash generation (in
+	 * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why
+	 * key sizes bigger than 64 bytes are not allowed.
+	 */
+	size_t key_size_pow2;
+
+	/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
+	size_t key_size_log2;
+
+	/* The key offset within the key buffer. */
+	size_t key_offset;
+
+	/* The real action data size. */
+	size_t action_data_size;
+
+	/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
+	 * next power of 2.
+	 */
+	size_t data_size_pow2;
+
+	/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t data_size_log2;
+
+	/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
+	size_t n_buckets;
+
+	/* Bucket mask. Purpose: replace modulo with bitmask and operation. */
+	size_t bucket_mask;
+
+	/* Total number of key bytes in the bucket, including the key padding bytes. There are
+	 * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
+	 */
+	size_t bucket_key_all_size;
+
+	/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
+	size_t bucket_size;
+
+	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t bucket_size_log2;
+
+	/* Timeout in CPU clock cycles. */
+	uint64_t key_timeout;
+
+	/* Total memory size. */
+	size_t total_size;
+};
+
+struct table {
+	/* Table parameters. */
+	struct table_params params;
+
+	/* Key mask. Array of *key_size* bytes. */
+	uint8_t key_mask0[RTE_CACHE_LINE_SIZE];
+
+	/* Table buckets. */
+	uint8_t buckets[0];
+} __rte_cache_aligned;
+
+static int
+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
+{
+	/* Check input parameters. */
+	if (!params ||
+	    !params->key_size ||
+	    (params->key_size > 64) ||
+	    !params->n_keys_max ||
+	    (params->n_keys_max > 1U << 31) ||
+	    !params->key_timeout)
+		return -EINVAL;
+
+	/* Key. */
+	p->key_size = params->key_size;
+
+	p->key_size_pow2 = rte_align64pow2(p->key_size);
+	if (p->key_size_pow2 < 8)
+		p->key_size_pow2 = 8;
+
+	p->key_size_log2 = __builtin_ctzll(p->key_size_pow2);
+
+	p->key_offset = params->key_offset;
+
+	/* Data. */
+	p->action_data_size = params->action_data_size;
+
+	p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
+
+	p->data_size_log2 = __builtin_ctzll(p->data_size_pow2);
+
+	/* Buckets. */
+	p->n_buckets = rte_align32pow2(params->n_keys_max);
+
+	p->bucket_mask = p->n_buckets - 1;
+
+	p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
+
+	p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
+					 p->bucket_key_all_size +
+					 TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
+
+	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
+
+	/* Timeout. */
+	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+
+	/* Total size. */
+	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
+
+	return 0;
+}
+
+static inline struct table_bucket *
+table_bucket_get(struct table *t, size_t bucket_id)
+{
+	return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
+}
+
+static inline uint8_t *
+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return &b->key[bucket_key_pos << t->params.key_size_log2];
+}
+
+static inline uint64_t *
+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return (uint64_t *)&b->key[t->params.bucket_key_all_size +
+				   (bucket_key_pos << t->params.data_size_log2)];
+}
+
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
+{
+	struct table_params p;
+	int status;
+
+	status = table_params_get(&p, params);
+
+	return status ? 0 : p.total_size;
+}
+
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
+{
+	struct table_params p;
+	struct table *t;
+	int status;
+
+	/* Check and process the input parameters. */
+	status = table_params_get(&p, params);
+	if (status)
+		return NULL;
+
+	/* Memory allocation. */
+	t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t)
+		return NULL;
+
+	/* Memory initialization. */
+	memcpy(&t->params, &p, sizeof(struct table_params));
+
+	if (params->key_mask0)
+		memcpy(t->key_mask0, params->key_mask0, params->key_size);
+	else
+		memset(t->key_mask0, 0xFF, params->key_size);
+
+	return t;
+}
+
+void
+rte_swx_table_learner_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	env_free(t, t->params.total_size);
+}
+
+struct mailbox {
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	struct table_bucket *bucket;
+
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	uint32_t input_sig;
+
+	/* Writer: lookup state 1. Reader(s): add(). */
+	uint8_t *input_key;
+
+	/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
+	uint32_t hit;
+
+	/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
+	size_t bucket_key_pos;
+
+	/* State. */
+	int state;
+};
+
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t input_time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+
+	switch (m->state) {
+	case 0: {
+		uint8_t *input_key;
+		struct table_bucket *b;
+		size_t bucket_id;
+		uint32_t input_sig;
+
+		input_key = &(*key)[t->params.key_offset];
+		input_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);
+		bucket_id = input_sig & t->params.bucket_mask;
+		b = table_bucket_get(t, bucket_id);
+
+		rte_prefetch0(b);
+		rte_prefetch0(&b->key[0]);
+		rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
+
+		m->bucket = b;
+		m->input_key = input_key;
+		m->input_sig = input_sig | 1;
+		m->state = 1;
+		return 0;
+	}
+
+	case 1: {
+		struct table_bucket *b = m->bucket;
+		uint32_t i;
+
+		/* Search the input key through the bucket keys. */
+		for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+			uint64_t time = b->time[i];
+			uint32_t sig = b->sig[i];
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint32_t key_size_pow2 = t->params.key_size_pow2;
+
+			time <<= 32;
+
+			if ((time > input_time) &&
+			    (sig == m->input_sig) &&
+			    table_keycmp(key, m->input_key, t->key_mask0, key_size_pow2)) {
+				uint64_t *data = table_bucket_data_get(t, b, i);
+
+				/* Hit. */
+				rte_prefetch0(data);
+
+				b->time[i] = (input_time + t->params.key_timeout) >> 32;
+
+				m->hit = 1;
+				m->bucket_key_pos = i;
+				m->state = 0;
+
+				*action_id = data[0];
+				*action_data = (uint8_t *)&data[1];
+				*hit = 1;
+				return 1;
+			}
+		}
+
+		/* Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+
+	default:
+		/* This state should never be reached. Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+}
+
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t input_time,
+			  uint64_t action_id,
+			  uint8_t *action_data)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b = m->bucket;
+	uint32_t i;
+
+	/* Lookup hit: The key, key signature and key time are already properly configured (the key
+	 * time was bumped by lookup), only the key data need to be updated.
+	 */
+	if (m->hit) {
+		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+
+		/* Install the key data. */
+		data[0] = action_id;
+		if (t->params.action_data_size && action_data)
+			memcpy(&data[1], action_data, t->params.action_data_size);
+
+		return 0;
+	}
+
+	/* Lookup miss: Search for a free position in the current bucket and install the key. */
+	for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+		uint64_t time = b->time[i];
+
+		time <<= 32;
+
+		/* Free position: Either there was never a key installed here, so the key time is
+		 * set to zero (the init value), which is always less than the current time, or this
+		 * position was used before, but the key expired (the key time is in the past).
+		 */
+		if (time < input_time) {
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint64_t *data = table_bucket_data_get(t, b, i);
+
+			/* Install the key. */
+			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			b->sig[i] = m->input_sig;
+			memcpy(key, m->input_key, t->params.key_size);
+
+			/* Install the key data. */
+			data[0] = action_id;
+			if (t->params.action_data_size && action_data)
+				memcpy(&data[1], action_data, t->params.action_data_size);
+
+			/* Mailbox. */
+			m->hit = 1;
+			m->bucket_key_pos = i;
+
+			return 0;
+		}
+	}
+
+	/* Bucket full. */
+	return 1;
+}
+
+void
+rte_swx_table_learner_delete(void *table __rte_unused,
+			     void *mailbox)
+{
+	struct mailbox *m = mailbox;
+
+	if (m->hit) {
+		struct table_bucket *b = m->bucket;
+
+		/* Expire the key. */
+		b->time[m->bucket_key_pos] = 0;
+
+		/* Mailbox. */
+		m->hit = 0;
+	}
+}
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
new file mode 100644
index 0000000000..d6ec733655
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Learner Table
+ *
+ * The learner table API.
+ *
+ * This table type is typically used for learning or connection tracking, where it allows for the
+ * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no
+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
+ * are automatically deleted from the table with no control plane intervention.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+/** Learner table creation parameters. */
+struct rte_swx_table_learner_params {
+	/** Key size in bytes. Must be non-zero. */
+	uint32_t key_size;
+
+	/** Offset of the first byte of the key within the key buffer. */
+	uint32_t key_offset;
+
+	/** Mask of *key_size* bytes logically laid over the bytes at positions
+	 * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify
+	 * which bits from the key buffer are part of the key and which ones are not. A bit value of
+	 * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while
+	 * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the
+	 * key, i.e. the *key_mask0* is an all-ones mask.
+	 */
+	uint8_t *key_mask0;
+
+	/** Maximum size (in bytes) of the action data. The data stored in the table for each entry
+	 * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.
+	 */
+	uint32_t action_data_size;
+
+	/** Maximum number of keys to be stored in the table together with their associated data. */
+	uint32_t n_keys_max;
+
+	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
+	 * deleted from the table after this many seconds.
+	 */
+	uint32_t key_timeout;
+};
+
+/**
+ * Learner table memory footprint get
+ *
+ * @param[in] params
+ *   Table create parameters.
+ * @return
+ *   Table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);
+
+/**
+ * Learner table mailbox size get
+ *
+ * The mailbox is used to store the context of a lookup operation that is in
+ * progress and it is passed as a parameter to the lookup operation. This allows
+ * for multiple concurrent lookup operations into the same table.
+ *
+ * @return
+ *   Table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void);
+
+/**
+ * Learner table create
+ *
+ * @param[in] params
+ *   Table creation parameters.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
+
+/**
+ * Learner table key lookup
+ *
+ * The table lookup operation searches a given key in the table and upon its completion it returns
+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case
+ * of lookup hit, the action_id and the action_data associated with the key are also returned.
+ *
+ * Multiple invocations of this function may be required in order to complete a single table lookup
+ * operation for a given table and a given lookup key. The completion of the table lookup operation
+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked
+ * again with exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and
+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent
+ * table key lookup and add operations into the same table.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current table lookup operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key
+ *   Lookup key. Its size must be equal to the table *key_size*.
+ * @param[out] action_id
+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid
+ *   when the function returns 1 and *hit* is set to true.
+ * @param[out] action_data
+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*
+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.
+ * @param[out] hit
+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero
+ *   (false) on table lookup miss.
+ * @return
+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation
+ *   is completed. No other return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit);
+
+/**
+ * Learner table key add
+ *
+ * This operation takes the latest key that was looked up in the table and adds it to the table with
+ * the given action ID and action data. Typically, this operation is only invoked when the latest
+ * lookup operation in the current table resulted in lookup miss.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[out] action_id
+ *   ID of the action associated with the key.
+ * @param[out] action_data
+ *   Action data for the *action_id* action.
+ * @return
+ *   0 on success, 1 or error (table full).
+ */
+__rte_experimental
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t time,
+			  uint64_t action_id,
+			  uint8_t *action_data);
+
+/**
+ * Learner table key delete
+ *
+ * This operation takes the latest key that was looked up in the table and deletes it from the
+ * table. Typically, this operation is only invoked to force the deletion of the key before the key
+ * expires on timeout due to inactivity.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_delete(void *table,
+			     void *mailbox);
+
+/**
+ * Learner table free
+ *
+ * @param[in] table
+ *   Table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index 29301480cb..f973a36ecc 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -36,4 +36,13 @@ EXPERIMENTAL {
 	rte_swx_table_selector_group_set;
 	rte_swx_table_selector_mailbox_size_get;
 	rte_swx_table_selector_select;
+
+	# added in 21.11
+	rte_swx_table_learner_add;
+	rte_swx_table_learner_create;
+	rte_swx_table_learner_delete;
+	rte_swx_table_learner_footprint_get;
+	rte_swx_table_learner_free;
+	rte_swx_table_learner_lookup;
+	rte_swx_table_learner_mailbox_size_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V2 2/4] pipeline: add support for learner tables
  2021-08-14 13:43 ` [dpdk-dev] [PATCH V2 1/4] table: add support learner tables Cristian Dumitrescu
@ 2021-08-14 13:43   ` Cristian Dumitrescu
  2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 3/4] examples/pipeline: " Cristian Dumitrescu
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:43 UTC (permalink / raw)
  To: dev

Add pipeline level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added more configuration consistency checks.

 lib/pipeline/rte_swx_ctl.c           |  479 +++++++++++-
 lib/pipeline/rte_swx_ctl.h           |  185 +++++
 lib/pipeline/rte_swx_pipeline.c      | 1041 ++++++++++++++++++++++++--
 lib/pipeline/rte_swx_pipeline.h      |   77 ++
 lib/pipeline/rte_swx_pipeline_spec.c |  470 +++++++++++-
 lib/pipeline/version.map             |    8 +
 6 files changed, 2205 insertions(+), 55 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..baad146c05 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;
@@ -746,7 +759,7 @@ struct action {
 	TAILQ_ENTRY(action) node;
 	char name[RTE_SWX_NAME_SIZE];
 	struct struct_type *st;
-	int *args_endianness; /* 0 = Host Byte Order (HBO). */
+	int *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */
 	struct instruction *instructions;
 	uint32_t n_instructions;
 	uint32_t id;
@@ -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,168 @@ 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
+action_has_nbo_args(struct action *a);
+
+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);
+	CHECK(!action_has_nbo_args(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 +7392,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 +8366,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 +9342,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,
 
@@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name)
 	return action_field_find(action, &name[2]);
 }
 
+static int
+action_has_nbo_args(struct action *a)
+{
+	uint32_t i;
+
+	/* Return if the action does not have any args. */
+	if (!a->st)
+		return 0; /* FALSE */
+
+	for (i = 0; i < a->st->n_fields; i++)
+		if (a->args_endianness[i])
+			return 1; /* TRUE */
+
+	return 0; /* FALSE */
+}
+
+static int
+action_does_learning(struct action *a)
+{
+	uint32_t i;
+
+	for (i = 0; i < a->n_instructions; i++)
+		switch (a->instructions[i].type) {
+			case INSTR_LEARNER_LEARN:
+				return 1; /* TRUE */
+
+			case INSTR_LEARNER_FORGET:
+				return 1; /* TRUE */
+
+			default:
+				continue;
+		}
+
+	return 0; /* FALSE */
+}
+
 int
 rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,
 			       const char *name,
@@ -9546,6 +9831,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);
 
@@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 		a = action_find(p, action_name);
 		CHECK(a, EINVAL);
+		CHECK(!action_does_learning(a), EINVAL);
 
 		action_data_size = a->st ? a->st->n_bits / 8 : 0;
 		if (action_data_size > action_data_size_max)
@@ -9964,6 +10251,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,73 +10509,604 @@ 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 (!ast) {
+		if (mf_name)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* 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);
+
+	/* 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);
+
+	/* 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];
+
+		CHECK(mf->n_bits == af->n_bits, EINVAL);
+	}
+
+	return 0;
+}
+
+static int
+learner_action_learning_check(struct rte_swx_pipeline *p,
+			      struct action *action,
+			      const char **action_names,
+			      uint32_t n_actions)
+{
+	uint32_t i;
+
+	/* For each "learn" instruction of the current action, check that the learned action (i.e.
+	 * the action passed as argument to the "learn" instruction) is also enabled for the
+	 * current learner table.
+	 */
+	for (i = 0; i < action->n_instructions; i++) {
+		struct instruction *instr = &action->instructions[i];
+		uint32_t found = 0, j;
+
+		if (instr->type != INSTR_LEARNER_LEARN)
+			continue;
+
+		for (j = 0; j < n_actions; j++) {
+			struct action *a;
+
+			a = action_find(p, action_names[j]);
+			if (!a)
+				return -EINVAL;
+
+			if (a->id == instr->learn.action_id)
+				found = 1;
+		}
+
+		if (!found)
+			return -EINVAL;
+	}
+
+	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;
+
+		status = learner_action_learning_check(p,
+						       a,
+						       params->action_names,
+						       params->n_actions);
+		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;
@@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p)
 			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 +11483,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 +11506,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 +11591,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 +11614,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 +11676,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 +11922,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 +12095,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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V2 3/4] examples/pipeline: add support for learner tables
  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   ` 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
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:43 UTC (permalink / raw)
  To: dev

Add application-level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 174 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index a29be05ef4..ad6e3db8d7 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1829,6 +1829,104 @@ cmd_pipeline_selector_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
 }
 
+static int
+pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				   const char *learner_name,
+				   FILE *file,
+				   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_learner_default_entry_read(p,
+									learner_name,
+									line,
+									&is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_default_entry_add(p,
+									learner_name,
+									entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_learner_default_help[] =
+"pipeline <pipeline_name> learner <learner_name> default <file_name>\n";
+
+static void
+cmd_pipeline_learner_default(char **tokens,
+			     uint32_t n_tokens,
+			     char *out,
+			     size_t out_size,
+			     void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *learner_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	learner_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_learner_default_entry_add(p->ctl,
+						    learner_name,
+						    file,
+						    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2503,6 +2601,64 @@ cmd_pipeline_stats(char **tokens,
 			out += strlen(out);
 		}
 	}
+
+	snprintf(out, out_size, "\nLearner tables:\n");
+	out_size -= strlen(out);
+	out += strlen(out);
+
+	for (i = 0; i < info.n_learners; i++) {
+		struct rte_swx_ctl_learner_info learner_info;
+		uint64_t n_pkts_action[info.n_actions];
+		struct rte_swx_learner_stats stats = {
+			.n_pkts_hit = 0,
+			.n_pkts_miss = 0,
+			.n_pkts_action = n_pkts_action,
+		};
+		uint32_t j;
+
+		status = rte_swx_ctl_learner_info_get(p->p, i, &learner_info);
+		if (status) {
+			snprintf(out, out_size, "Learner table info get error.");
+			return;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_stats_read(p->p, learner_info.name, &stats);
+		if (status) {
+			snprintf(out, out_size, "Learner table stats read error.");
+			return;
+		}
+
+		snprintf(out, out_size, "\tLearner table %s:\n"
+			"\t\tHit (packets): %" PRIu64 "\n"
+			"\t\tMiss (packets): %" PRIu64 "\n"
+			"\t\tLearn OK (packets): %" PRIu64 "\n"
+			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tForget (packets): %" PRIu64 "\n",
+			learner_info.name,
+			stats.n_pkts_hit,
+			stats.n_pkts_miss,
+			stats.n_pkts_learn_ok,
+			stats.n_pkts_learn_err,
+			stats.n_pkts_forget);
+		out_size -= strlen(out);
+		out += strlen(out);
+
+		for (j = 0; j < info.n_actions; j++) {
+			struct rte_swx_ctl_action_info action_info;
+
+			status = rte_swx_ctl_action_info_get(p->p, j, &action_info);
+			if (status) {
+				snprintf(out, out_size, "Action info get error.");
+				return;
+			}
+
+			snprintf(out, out_size, "\t\tAction %s (packets): %" PRIu64 "\n",
+				action_info.name,
+				stats.n_pkts_action[j]);
+			out_size -= strlen(out);
+			out += strlen(out);
+		}
+	}
 }
 
 static const char cmd_thread_pipeline_enable_help[] =
@@ -2634,6 +2790,7 @@ cmd_help(char **tokens,
 			"\tpipeline selector group member add\n"
 			"\tpipeline selector group member delete\n"
 			"\tpipeline selector show\n"
+			"\tpipeline learner default\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2783,6 +2940,15 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "learner") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_learner_default_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -3031,6 +3197,14 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "learner") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_learner_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V2 4/4] examples/pipeline: add learner table example
  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   ` Cristian Dumitrescu
  2021-08-14 13:59   ` [dpdk-dev] [PATCH V3 1/4] table: add support learner tables Cristian Dumitrescu
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:43 UTC (permalink / raw)
  To: dev

Added the files to illustrate the learner table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added description to the .spec file.

 examples/pipeline/examples/learner.cli  |  37 +++++++
 examples/pipeline/examples/learner.spec | 127 ++++++++++++++++++++++++
 2 files changed, 164 insertions(+)
 create mode 100644 examples/pipeline/examples/learner.cli
 create mode 100644 examples/pipeline/examples/learner.spec

diff --git a/examples/pipeline/examples/learner.cli b/examples/pipeline/examples/learner.cli
new file mode 100644
index 0000000000..af7792624f
--- /dev/null
+++ b/examples/pipeline/examples/learner.cli
@@ -0,0 +1,37 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+;
+; Customize the LINK parameters to match your setup.
+;
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+;
+; PIPELINE0 setup.
+;
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/learner.spec
+
+;
+; Pipelines-to-threads mapping.
+;
+thread 1 pipeline PIPELINE0 enable
+
+; Once the application has started, the command to get the CLI prompt is: telnet 0.0.0.0 8086
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
new file mode 100644
index 0000000000..d635422282
--- /dev/null
+++ b/examples/pipeline/examples/learner.spec
@@ -0,0 +1,127 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; The learner tables are very useful for learning and connection tracking.
+;
+; As opposed to regular tables, which are read-only for the data plane, the learner tables can be
+; updated by the data plane without any control plane intervention. The "learning" process typically
+; takes place by having the default action (i.e. the table action which is executed on lookup miss)
+; explicitly add to the table with a specific action the key that just missed the lookup operation.
+; Each table key expires automatically after a configurable timeout period if not hit during this
+; interval.
+;
+; This example demonstrates a simple connection tracking setup, where the connections are identified
+; by the IPv4 destination address. The forwarding action assigned to each new connection gets the
+; output port as argument, with the output port of each connection generated by a counter that is
+; persistent between packets. On top of the usual table stats, the learner table stats include the
+; number of packets with learning related events.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+
+	// Arguments for the "fwd_action" action.
+	bit<32> fwd_action_arg_port_out
+}
+
+metadata instanceof metadata_t
+
+//
+// Registers.
+//
+regarray counter size 1 initval 0
+
+//
+// Actions
+//
+struct fwd_action_args_t {
+	bit<32> port_out
+}
+
+action fwd_action args instanceof fwd_action_args_t {
+	mov m.port_out t.port_out
+	return
+}
+
+action learn_action args none {
+	// Read current counter value into m.fwd_action_arg_port_out.
+	regrd m.fwd_action_arg_port_out counter 0
+
+	// Increment the counter.
+	regadd counter 0 1
+
+	// Limit the output port values to 0 .. 3.
+	and m.fwd_action_arg_port_out 3
+
+	// Add the current lookup key to the table with fwd_action as the key action. The action
+	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
+	// packet meta-data fields have to be written before the "learn" instruction is invoked.
+	learn fwd_action
+
+	// Send the current packet to the same output port.
+	mov m.port_out m.fwd_action_arg_port_out
+
+	return
+}
+
+//
+// Tables.
+//
+learner fwd_table {
+	key {
+		h.ipv4.dst_addr
+	}
+
+	actions {
+		fwd_action args m.fwd_action_arg_port_out
+
+		learn_action args none
+	}
+
+	default_action learn_action args none
+
+	size 1048576
+
+	timeout 120
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	table fwd_table
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V3 1/4] table: add support learner tables
  2021-08-14 13:43 ` [dpdk-dev] [PATCH V2 1/4] table: add support learner tables Cristian Dumitrescu
                     ` (2 preceding siblings ...)
  2021-08-14 13:43   ` [dpdk-dev] [PATCH V2 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
@ 2021-08-14 13:59   ` Cristian Dumitrescu
  2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 2/4] pipeline: add support for " Cristian Dumitrescu
                       ` (3 more replies)
  3 siblings, 4 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:59 UTC (permalink / raw)
  To: dev

A learner table is typically used for learning or connection tracking,
where it allows for the implementation of the "add on miss" scenario:
whenever the lookup key is not found in the table (lookup miss), the
data plane can decide to add this key to the table with a given action
with no control plane intervention. Likewise, the table keys expire
based on a configurable timeout and are automatically deleted from the
table with no control plane intervention.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-18023 ("[V2,1/5] pipeline: prepare for variable size headers")

V2: fixed one "line too long" coding style warning.

 lib/table/meson.build             |   2 +
 lib/table/rte_swx_table_learner.c | 617 ++++++++++++++++++++++++++++++
 lib/table/rte_swx_table_learner.h | 206 ++++++++++
 lib/table/version.map             |   9 +
 4 files changed, 834 insertions(+)
 create mode 100644 lib/table/rte_swx_table_learner.c
 create mode 100644 lib/table/rte_swx_table_learner.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index a1384456a9..ac1f1aac27 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+        'rte_swx_table_learner.c',
         'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
@@ -21,6 +22,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+        'rte_swx_table_learner.h',
         'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
new file mode 100644
index 0000000000..c3c840ff06
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.c
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_learner.h"
+
+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
+#endif
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+/*
+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
+ */
+static inline uint32_t
+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
+{
+	uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;
+
+	switch (n_bytes) {
+	case 8: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint32_t result = 1;
+
+		if (xor0)
+			result = 0;
+		return result;
+	}
+
+	case 16: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t or = xor0 | xor1;
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 32: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t or = (xor0 | xor1) | (xor2 | xor3);
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 64: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);
+		uint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);
+		uint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);
+		uint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);
+		uint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |
+			      ((xor4 | xor5) | (xor6 | xor7));
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	default: {
+		uint32_t i;
+
+		for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+			if (a64[i] != (b64[i] & b_mask64[i]))
+				return 0;
+		return 1;
+	}
+	}
+}
+
+#define TABLE_KEYS_PER_BUCKET 4
+
+#define TABLE_BUCKET_PAD_SIZE \
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+
+struct table_bucket {
+	uint32_t time[TABLE_KEYS_PER_BUCKET];
+	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
+	uint8_t key[0];
+};
+
+struct table_params {
+	/* The real key size. Must be non-zero. */
+	size_t key_size;
+
+	/* They key size upgrated to the next power of 2. This used for hash generation (in
+	 * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why
+	 * key sizes bigger than 64 bytes are not allowed.
+	 */
+	size_t key_size_pow2;
+
+	/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
+	size_t key_size_log2;
+
+	/* The key offset within the key buffer. */
+	size_t key_offset;
+
+	/* The real action data size. */
+	size_t action_data_size;
+
+	/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
+	 * next power of 2.
+	 */
+	size_t data_size_pow2;
+
+	/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t data_size_log2;
+
+	/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
+	size_t n_buckets;
+
+	/* Bucket mask. Purpose: replace modulo with bitmask and operation. */
+	size_t bucket_mask;
+
+	/* Total number of key bytes in the bucket, including the key padding bytes. There are
+	 * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
+	 */
+	size_t bucket_key_all_size;
+
+	/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
+	size_t bucket_size;
+
+	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t bucket_size_log2;
+
+	/* Timeout in CPU clock cycles. */
+	uint64_t key_timeout;
+
+	/* Total memory size. */
+	size_t total_size;
+};
+
+struct table {
+	/* Table parameters. */
+	struct table_params params;
+
+	/* Key mask. Array of *key_size* bytes. */
+	uint8_t key_mask0[RTE_CACHE_LINE_SIZE];
+
+	/* Table buckets. */
+	uint8_t buckets[0];
+} __rte_cache_aligned;
+
+static int
+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
+{
+	/* Check input parameters. */
+	if (!params ||
+	    !params->key_size ||
+	    (params->key_size > 64) ||
+	    !params->n_keys_max ||
+	    (params->n_keys_max > 1U << 31) ||
+	    !params->key_timeout)
+		return -EINVAL;
+
+	/* Key. */
+	p->key_size = params->key_size;
+
+	p->key_size_pow2 = rte_align64pow2(p->key_size);
+	if (p->key_size_pow2 < 8)
+		p->key_size_pow2 = 8;
+
+	p->key_size_log2 = __builtin_ctzll(p->key_size_pow2);
+
+	p->key_offset = params->key_offset;
+
+	/* Data. */
+	p->action_data_size = params->action_data_size;
+
+	p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
+
+	p->data_size_log2 = __builtin_ctzll(p->data_size_pow2);
+
+	/* Buckets. */
+	p->n_buckets = rte_align32pow2(params->n_keys_max);
+
+	p->bucket_mask = p->n_buckets - 1;
+
+	p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
+
+	p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
+					 p->bucket_key_all_size +
+					 TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
+
+	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
+
+	/* Timeout. */
+	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+
+	/* Total size. */
+	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
+
+	return 0;
+}
+
+static inline struct table_bucket *
+table_bucket_get(struct table *t, size_t bucket_id)
+{
+	return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
+}
+
+static inline uint8_t *
+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return &b->key[bucket_key_pos << t->params.key_size_log2];
+}
+
+static inline uint64_t *
+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return (uint64_t *)&b->key[t->params.bucket_key_all_size +
+				   (bucket_key_pos << t->params.data_size_log2)];
+}
+
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
+{
+	struct table_params p;
+	int status;
+
+	status = table_params_get(&p, params);
+
+	return status ? 0 : p.total_size;
+}
+
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
+{
+	struct table_params p;
+	struct table *t;
+	int status;
+
+	/* Check and process the input parameters. */
+	status = table_params_get(&p, params);
+	if (status)
+		return NULL;
+
+	/* Memory allocation. */
+	t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t)
+		return NULL;
+
+	/* Memory initialization. */
+	memcpy(&t->params, &p, sizeof(struct table_params));
+
+	if (params->key_mask0)
+		memcpy(t->key_mask0, params->key_mask0, params->key_size);
+	else
+		memset(t->key_mask0, 0xFF, params->key_size);
+
+	return t;
+}
+
+void
+rte_swx_table_learner_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	env_free(t, t->params.total_size);
+}
+
+struct mailbox {
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	struct table_bucket *bucket;
+
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	uint32_t input_sig;
+
+	/* Writer: lookup state 1. Reader(s): add(). */
+	uint8_t *input_key;
+
+	/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
+	uint32_t hit;
+
+	/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
+	size_t bucket_key_pos;
+
+	/* State. */
+	int state;
+};
+
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t input_time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+
+	switch (m->state) {
+	case 0: {
+		uint8_t *input_key;
+		struct table_bucket *b;
+		size_t bucket_id;
+		uint32_t input_sig;
+
+		input_key = &(*key)[t->params.key_offset];
+		input_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);
+		bucket_id = input_sig & t->params.bucket_mask;
+		b = table_bucket_get(t, bucket_id);
+
+		rte_prefetch0(b);
+		rte_prefetch0(&b->key[0]);
+		rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
+
+		m->bucket = b;
+		m->input_key = input_key;
+		m->input_sig = input_sig | 1;
+		m->state = 1;
+		return 0;
+	}
+
+	case 1: {
+		struct table_bucket *b = m->bucket;
+		uint32_t i;
+
+		/* Search the input key through the bucket keys. */
+		for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+			uint64_t time = b->time[i];
+			uint32_t sig = b->sig[i];
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint32_t key_size_pow2 = t->params.key_size_pow2;
+
+			time <<= 32;
+
+			if ((time > input_time) &&
+			    (sig == m->input_sig) &&
+			    table_keycmp(key, m->input_key, t->key_mask0, key_size_pow2)) {
+				uint64_t *data = table_bucket_data_get(t, b, i);
+
+				/* Hit. */
+				rte_prefetch0(data);
+
+				b->time[i] = (input_time + t->params.key_timeout) >> 32;
+
+				m->hit = 1;
+				m->bucket_key_pos = i;
+				m->state = 0;
+
+				*action_id = data[0];
+				*action_data = (uint8_t *)&data[1];
+				*hit = 1;
+				return 1;
+			}
+		}
+
+		/* Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+
+	default:
+		/* This state should never be reached. Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+}
+
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t input_time,
+			  uint64_t action_id,
+			  uint8_t *action_data)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b = m->bucket;
+	uint32_t i;
+
+	/* Lookup hit: The key, key signature and key time are already properly configured (the key
+	 * time was bumped by lookup), only the key data need to be updated.
+	 */
+	if (m->hit) {
+		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+
+		/* Install the key data. */
+		data[0] = action_id;
+		if (t->params.action_data_size && action_data)
+			memcpy(&data[1], action_data, t->params.action_data_size);
+
+		return 0;
+	}
+
+	/* Lookup miss: Search for a free position in the current bucket and install the key. */
+	for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+		uint64_t time = b->time[i];
+
+		time <<= 32;
+
+		/* Free position: Either there was never a key installed here, so the key time is
+		 * set to zero (the init value), which is always less than the current time, or this
+		 * position was used before, but the key expired (the key time is in the past).
+		 */
+		if (time < input_time) {
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint64_t *data = table_bucket_data_get(t, b, i);
+
+			/* Install the key. */
+			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			b->sig[i] = m->input_sig;
+			memcpy(key, m->input_key, t->params.key_size);
+
+			/* Install the key data. */
+			data[0] = action_id;
+			if (t->params.action_data_size && action_data)
+				memcpy(&data[1], action_data, t->params.action_data_size);
+
+			/* Mailbox. */
+			m->hit = 1;
+			m->bucket_key_pos = i;
+
+			return 0;
+		}
+	}
+
+	/* Bucket full. */
+	return 1;
+}
+
+void
+rte_swx_table_learner_delete(void *table __rte_unused,
+			     void *mailbox)
+{
+	struct mailbox *m = mailbox;
+
+	if (m->hit) {
+		struct table_bucket *b = m->bucket;
+
+		/* Expire the key. */
+		b->time[m->bucket_key_pos] = 0;
+
+		/* Mailbox. */
+		m->hit = 0;
+	}
+}
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
new file mode 100644
index 0000000000..d6ec733655
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Learner Table
+ *
+ * The learner table API.
+ *
+ * This table type is typically used for learning or connection tracking, where it allows for the
+ * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no
+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
+ * are automatically deleted from the table with no control plane intervention.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+/** Learner table creation parameters. */
+struct rte_swx_table_learner_params {
+	/** Key size in bytes. Must be non-zero. */
+	uint32_t key_size;
+
+	/** Offset of the first byte of the key within the key buffer. */
+	uint32_t key_offset;
+
+	/** Mask of *key_size* bytes logically laid over the bytes at positions
+	 * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify
+	 * which bits from the key buffer are part of the key and which ones are not. A bit value of
+	 * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while
+	 * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the
+	 * key, i.e. the *key_mask0* is an all-ones mask.
+	 */
+	uint8_t *key_mask0;
+
+	/** Maximum size (in bytes) of the action data. The data stored in the table for each entry
+	 * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.
+	 */
+	uint32_t action_data_size;
+
+	/** Maximum number of keys to be stored in the table together with their associated data. */
+	uint32_t n_keys_max;
+
+	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
+	 * deleted from the table after this many seconds.
+	 */
+	uint32_t key_timeout;
+};
+
+/**
+ * Learner table memory footprint get
+ *
+ * @param[in] params
+ *   Table create parameters.
+ * @return
+ *   Table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);
+
+/**
+ * Learner table mailbox size get
+ *
+ * The mailbox is used to store the context of a lookup operation that is in
+ * progress and it is passed as a parameter to the lookup operation. This allows
+ * for multiple concurrent lookup operations into the same table.
+ *
+ * @return
+ *   Table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void);
+
+/**
+ * Learner table create
+ *
+ * @param[in] params
+ *   Table creation parameters.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
+
+/**
+ * Learner table key lookup
+ *
+ * The table lookup operation searches a given key in the table and upon its completion it returns
+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case
+ * of lookup hit, the action_id and the action_data associated with the key are also returned.
+ *
+ * Multiple invocations of this function may be required in order to complete a single table lookup
+ * operation for a given table and a given lookup key. The completion of the table lookup operation
+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked
+ * again with exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and
+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent
+ * table key lookup and add operations into the same table.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current table lookup operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key
+ *   Lookup key. Its size must be equal to the table *key_size*.
+ * @param[out] action_id
+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid
+ *   when the function returns 1 and *hit* is set to true.
+ * @param[out] action_data
+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*
+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.
+ * @param[out] hit
+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero
+ *   (false) on table lookup miss.
+ * @return
+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation
+ *   is completed. No other return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit);
+
+/**
+ * Learner table key add
+ *
+ * This operation takes the latest key that was looked up in the table and adds it to the table with
+ * the given action ID and action data. Typically, this operation is only invoked when the latest
+ * lookup operation in the current table resulted in lookup miss.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[out] action_id
+ *   ID of the action associated with the key.
+ * @param[out] action_data
+ *   Action data for the *action_id* action.
+ * @return
+ *   0 on success, 1 or error (table full).
+ */
+__rte_experimental
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t time,
+			  uint64_t action_id,
+			  uint8_t *action_data);
+
+/**
+ * Learner table key delete
+ *
+ * This operation takes the latest key that was looked up in the table and deletes it from the
+ * table. Typically, this operation is only invoked to force the deletion of the key before the key
+ * expires on timeout due to inactivity.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_delete(void *table,
+			     void *mailbox);
+
+/**
+ * Learner table free
+ *
+ * @param[in] table
+ *   Table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index 29301480cb..f973a36ecc 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -36,4 +36,13 @@ EXPERIMENTAL {
 	rte_swx_table_selector_group_set;
 	rte_swx_table_selector_mailbox_size_get;
 	rte_swx_table_selector_select;
+
+	# added in 21.11
+	rte_swx_table_learner_add;
+	rte_swx_table_learner_create;
+	rte_swx_table_learner_delete;
+	rte_swx_table_learner_footprint_get;
+	rte_swx_table_learner_free;
+	rte_swx_table_learner_lookup;
+	rte_swx_table_learner_mailbox_size_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V3 2/4] pipeline: add support for learner tables
  2021-08-14 13:59   ` [dpdk-dev] [PATCH V3 1/4] table: add support learner tables Cristian Dumitrescu
@ 2021-08-14 13:59     ` Cristian Dumitrescu
  2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 3/4] examples/pipeline: " Cristian Dumitrescu
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:59 UTC (permalink / raw)
  To: dev

Add pipeline level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added more configuration consistency checks.
V3: Fixed one coding style indentation error.

 lib/pipeline/rte_swx_ctl.c           |  479 +++++++++++-
 lib/pipeline/rte_swx_ctl.h           |  185 +++++
 lib/pipeline/rte_swx_pipeline.c      | 1041 ++++++++++++++++++++++++--
 lib/pipeline/rte_swx_pipeline.h      |   77 ++
 lib/pipeline/rte_swx_pipeline_spec.c |  470 +++++++++++-
 lib/pipeline/version.map             |    8 +
 6 files changed, 2205 insertions(+), 55 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..a85d80289d 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;
@@ -746,7 +759,7 @@ struct action {
 	TAILQ_ENTRY(action) node;
 	char name[RTE_SWX_NAME_SIZE];
 	struct struct_type *st;
-	int *args_endianness; /* 0 = Host Byte Order (HBO). */
+	int *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */
 	struct instruction *instructions;
 	uint32_t n_instructions;
 	uint32_t id;
@@ -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,168 @@ 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
+action_has_nbo_args(struct action *a);
+
+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);
+	CHECK(!action_has_nbo_args(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 +7392,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 +8366,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 +9342,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,
 
@@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name)
 	return action_field_find(action, &name[2]);
 }
 
+static int
+action_has_nbo_args(struct action *a)
+{
+	uint32_t i;
+
+	/* Return if the action does not have any args. */
+	if (!a->st)
+		return 0; /* FALSE */
+
+	for (i = 0; i < a->st->n_fields; i++)
+		if (a->args_endianness[i])
+			return 1; /* TRUE */
+
+	return 0; /* FALSE */
+}
+
+static int
+action_does_learning(struct action *a)
+{
+	uint32_t i;
+
+	for (i = 0; i < a->n_instructions; i++)
+		switch (a->instructions[i].type) {
+		case INSTR_LEARNER_LEARN:
+			return 1; /* TRUE */
+
+		case INSTR_LEARNER_FORGET:
+			return 1; /* TRUE */
+
+		default:
+			continue;
+		}
+
+	return 0; /* FALSE */
+}
+
 int
 rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,
 			       const char *name,
@@ -9546,6 +9831,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);
 
@@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 		a = action_find(p, action_name);
 		CHECK(a, EINVAL);
+		CHECK(!action_does_learning(a), EINVAL);
 
 		action_data_size = a->st ? a->st->n_bits / 8 : 0;
 		if (action_data_size > action_data_size_max)
@@ -9964,6 +10251,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,73 +10509,604 @@ 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 (!ast) {
+		if (mf_name)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* 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);
+
+	/* 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);
+
+	/* 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];
+
+		CHECK(mf->n_bits == af->n_bits, EINVAL);
+	}
+
+	return 0;
+}
+
+static int
+learner_action_learning_check(struct rte_swx_pipeline *p,
+			      struct action *action,
+			      const char **action_names,
+			      uint32_t n_actions)
+{
+	uint32_t i;
+
+	/* For each "learn" instruction of the current action, check that the learned action (i.e.
+	 * the action passed as argument to the "learn" instruction) is also enabled for the
+	 * current learner table.
+	 */
+	for (i = 0; i < action->n_instructions; i++) {
+		struct instruction *instr = &action->instructions[i];
+		uint32_t found = 0, j;
+
+		if (instr->type != INSTR_LEARNER_LEARN)
+			continue;
+
+		for (j = 0; j < n_actions; j++) {
+			struct action *a;
+
+			a = action_find(p, action_names[j]);
+			if (!a)
+				return -EINVAL;
+
+			if (a->id == instr->learn.action_id)
+				found = 1;
+		}
+
+		if (!found)
+			return -EINVAL;
+	}
+
+	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;
+
+		status = learner_action_learning_check(p,
+						       a,
+						       params->action_names,
+						       params->n_actions);
+		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;
@@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p)
 			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 +11483,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 +11506,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 +11591,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 +11614,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 +11676,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 +11922,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 +12095,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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V3 3/4] examples/pipeline: add support for learner tables
  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     ` 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
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:59 UTC (permalink / raw)
  To: dev

Add application-level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 174 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index a29be05ef4..ad6e3db8d7 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1829,6 +1829,104 @@ cmd_pipeline_selector_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
 }
 
+static int
+pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				   const char *learner_name,
+				   FILE *file,
+				   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_learner_default_entry_read(p,
+									learner_name,
+									line,
+									&is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_default_entry_add(p,
+									learner_name,
+									entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_learner_default_help[] =
+"pipeline <pipeline_name> learner <learner_name> default <file_name>\n";
+
+static void
+cmd_pipeline_learner_default(char **tokens,
+			     uint32_t n_tokens,
+			     char *out,
+			     size_t out_size,
+			     void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *learner_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	learner_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_learner_default_entry_add(p->ctl,
+						    learner_name,
+						    file,
+						    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2503,6 +2601,64 @@ cmd_pipeline_stats(char **tokens,
 			out += strlen(out);
 		}
 	}
+
+	snprintf(out, out_size, "\nLearner tables:\n");
+	out_size -= strlen(out);
+	out += strlen(out);
+
+	for (i = 0; i < info.n_learners; i++) {
+		struct rte_swx_ctl_learner_info learner_info;
+		uint64_t n_pkts_action[info.n_actions];
+		struct rte_swx_learner_stats stats = {
+			.n_pkts_hit = 0,
+			.n_pkts_miss = 0,
+			.n_pkts_action = n_pkts_action,
+		};
+		uint32_t j;
+
+		status = rte_swx_ctl_learner_info_get(p->p, i, &learner_info);
+		if (status) {
+			snprintf(out, out_size, "Learner table info get error.");
+			return;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_stats_read(p->p, learner_info.name, &stats);
+		if (status) {
+			snprintf(out, out_size, "Learner table stats read error.");
+			return;
+		}
+
+		snprintf(out, out_size, "\tLearner table %s:\n"
+			"\t\tHit (packets): %" PRIu64 "\n"
+			"\t\tMiss (packets): %" PRIu64 "\n"
+			"\t\tLearn OK (packets): %" PRIu64 "\n"
+			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tForget (packets): %" PRIu64 "\n",
+			learner_info.name,
+			stats.n_pkts_hit,
+			stats.n_pkts_miss,
+			stats.n_pkts_learn_ok,
+			stats.n_pkts_learn_err,
+			stats.n_pkts_forget);
+		out_size -= strlen(out);
+		out += strlen(out);
+
+		for (j = 0; j < info.n_actions; j++) {
+			struct rte_swx_ctl_action_info action_info;
+
+			status = rte_swx_ctl_action_info_get(p->p, j, &action_info);
+			if (status) {
+				snprintf(out, out_size, "Action info get error.");
+				return;
+			}
+
+			snprintf(out, out_size, "\t\tAction %s (packets): %" PRIu64 "\n",
+				action_info.name,
+				stats.n_pkts_action[j]);
+			out_size -= strlen(out);
+			out += strlen(out);
+		}
+	}
 }
 
 static const char cmd_thread_pipeline_enable_help[] =
@@ -2634,6 +2790,7 @@ cmd_help(char **tokens,
 			"\tpipeline selector group member add\n"
 			"\tpipeline selector group member delete\n"
 			"\tpipeline selector show\n"
+			"\tpipeline learner default\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2783,6 +2940,15 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "learner") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_learner_default_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -3031,6 +3197,14 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "learner") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_learner_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V3 4/4] examples/pipeline: add learner table example
  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     ` Cristian Dumitrescu
  2021-08-16 12:22     ` [dpdk-dev] [PATCH V4 1/4] table: add support learner tables Cristian Dumitrescu
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-14 13:59 UTC (permalink / raw)
  To: dev

Added the files to illustrate the learner table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added description to the .spec file.

 examples/pipeline/examples/learner.cli  |  37 +++++++
 examples/pipeline/examples/learner.spec | 127 ++++++++++++++++++++++++
 2 files changed, 164 insertions(+)
 create mode 100644 examples/pipeline/examples/learner.cli
 create mode 100644 examples/pipeline/examples/learner.spec

diff --git a/examples/pipeline/examples/learner.cli b/examples/pipeline/examples/learner.cli
new file mode 100644
index 0000000000..af7792624f
--- /dev/null
+++ b/examples/pipeline/examples/learner.cli
@@ -0,0 +1,37 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+;
+; Customize the LINK parameters to match your setup.
+;
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+;
+; PIPELINE0 setup.
+;
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/learner.spec
+
+;
+; Pipelines-to-threads mapping.
+;
+thread 1 pipeline PIPELINE0 enable
+
+; Once the application has started, the command to get the CLI prompt is: telnet 0.0.0.0 8086
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
new file mode 100644
index 0000000000..d635422282
--- /dev/null
+++ b/examples/pipeline/examples/learner.spec
@@ -0,0 +1,127 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; The learner tables are very useful for learning and connection tracking.
+;
+; As opposed to regular tables, which are read-only for the data plane, the learner tables can be
+; updated by the data plane without any control plane intervention. The "learning" process typically
+; takes place by having the default action (i.e. the table action which is executed on lookup miss)
+; explicitly add to the table with a specific action the key that just missed the lookup operation.
+; Each table key expires automatically after a configurable timeout period if not hit during this
+; interval.
+;
+; This example demonstrates a simple connection tracking setup, where the connections are identified
+; by the IPv4 destination address. The forwarding action assigned to each new connection gets the
+; output port as argument, with the output port of each connection generated by a counter that is
+; persistent between packets. On top of the usual table stats, the learner table stats include the
+; number of packets with learning related events.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+
+	// Arguments for the "fwd_action" action.
+	bit<32> fwd_action_arg_port_out
+}
+
+metadata instanceof metadata_t
+
+//
+// Registers.
+//
+regarray counter size 1 initval 0
+
+//
+// Actions
+//
+struct fwd_action_args_t {
+	bit<32> port_out
+}
+
+action fwd_action args instanceof fwd_action_args_t {
+	mov m.port_out t.port_out
+	return
+}
+
+action learn_action args none {
+	// Read current counter value into m.fwd_action_arg_port_out.
+	regrd m.fwd_action_arg_port_out counter 0
+
+	// Increment the counter.
+	regadd counter 0 1
+
+	// Limit the output port values to 0 .. 3.
+	and m.fwd_action_arg_port_out 3
+
+	// Add the current lookup key to the table with fwd_action as the key action. The action
+	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
+	// packet meta-data fields have to be written before the "learn" instruction is invoked.
+	learn fwd_action
+
+	// Send the current packet to the same output port.
+	mov m.port_out m.fwd_action_arg_port_out
+
+	return
+}
+
+//
+// Tables.
+//
+learner fwd_table {
+	key {
+		h.ipv4.dst_addr
+	}
+
+	actions {
+		fwd_action args m.fwd_action_arg_port_out
+
+		learn_action args none
+	}
+
+	default_action learn_action args none
+
+	size 1048576
+
+	timeout 120
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	table fwd_table
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V4 1/4] table: add support learner tables
  2021-08-14 13:59   ` [dpdk-dev] [PATCH V3 1/4] table: add support learner tables Cristian Dumitrescu
                       ` (2 preceding siblings ...)
  2021-08-14 13:59     ` [dpdk-dev] [PATCH V3 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
@ 2021-08-16 12:22     ` Cristian Dumitrescu
  2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 2/4] pipeline: add support for " Cristian Dumitrescu
                         ` (3 more replies)
  3 siblings, 4 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-16 12:22 UTC (permalink / raw)
  To: dev

A learner table is typically used for learning or connection tracking,
where it allows for the implementation of the "add on miss" scenario:
whenever the lookup key is not found in the table (lookup miss), the
data plane can decide to add this key to the table with a given action
with no control plane intervention. Likewise, the table keys expire
based on a configurable timeout and are automatically deleted from the
table with no control plane intervention.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-18023 ("[V2,1/5] pipeline: prepare for variable size headers")

V2: fixed one "line too long" coding style warning.

 lib/table/meson.build             |   2 +
 lib/table/rte_swx_table_learner.c | 617 ++++++++++++++++++++++++++++++
 lib/table/rte_swx_table_learner.h | 206 ++++++++++
 lib/table/version.map             |   9 +
 4 files changed, 834 insertions(+)
 create mode 100644 lib/table/rte_swx_table_learner.c
 create mode 100644 lib/table/rte_swx_table_learner.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index a1384456a9..ac1f1aac27 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+        'rte_swx_table_learner.c',
         'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
@@ -21,6 +22,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+        'rte_swx_table_learner.h',
         'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
new file mode 100644
index 0000000000..c3c840ff06
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.c
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_learner.h"
+
+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
+#endif
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+/*
+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
+ */
+static inline uint32_t
+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
+{
+	uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;
+
+	switch (n_bytes) {
+	case 8: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint32_t result = 1;
+
+		if (xor0)
+			result = 0;
+		return result;
+	}
+
+	case 16: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t or = xor0 | xor1;
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 32: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t or = (xor0 | xor1) | (xor2 | xor3);
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 64: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);
+		uint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);
+		uint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);
+		uint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);
+		uint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |
+			      ((xor4 | xor5) | (xor6 | xor7));
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	default: {
+		uint32_t i;
+
+		for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+			if (a64[i] != (b64[i] & b_mask64[i]))
+				return 0;
+		return 1;
+	}
+	}
+}
+
+#define TABLE_KEYS_PER_BUCKET 4
+
+#define TABLE_BUCKET_PAD_SIZE \
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+
+struct table_bucket {
+	uint32_t time[TABLE_KEYS_PER_BUCKET];
+	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
+	uint8_t key[0];
+};
+
+struct table_params {
+	/* The real key size. Must be non-zero. */
+	size_t key_size;
+
+	/* They key size upgrated to the next power of 2. This used for hash generation (in
+	 * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why
+	 * key sizes bigger than 64 bytes are not allowed.
+	 */
+	size_t key_size_pow2;
+
+	/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
+	size_t key_size_log2;
+
+	/* The key offset within the key buffer. */
+	size_t key_offset;
+
+	/* The real action data size. */
+	size_t action_data_size;
+
+	/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
+	 * next power of 2.
+	 */
+	size_t data_size_pow2;
+
+	/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t data_size_log2;
+
+	/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
+	size_t n_buckets;
+
+	/* Bucket mask. Purpose: replace modulo with bitmask and operation. */
+	size_t bucket_mask;
+
+	/* Total number of key bytes in the bucket, including the key padding bytes. There are
+	 * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
+	 */
+	size_t bucket_key_all_size;
+
+	/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
+	size_t bucket_size;
+
+	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t bucket_size_log2;
+
+	/* Timeout in CPU clock cycles. */
+	uint64_t key_timeout;
+
+	/* Total memory size. */
+	size_t total_size;
+};
+
+struct table {
+	/* Table parameters. */
+	struct table_params params;
+
+	/* Key mask. Array of *key_size* bytes. */
+	uint8_t key_mask0[RTE_CACHE_LINE_SIZE];
+
+	/* Table buckets. */
+	uint8_t buckets[0];
+} __rte_cache_aligned;
+
+static int
+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
+{
+	/* Check input parameters. */
+	if (!params ||
+	    !params->key_size ||
+	    (params->key_size > 64) ||
+	    !params->n_keys_max ||
+	    (params->n_keys_max > 1U << 31) ||
+	    !params->key_timeout)
+		return -EINVAL;
+
+	/* Key. */
+	p->key_size = params->key_size;
+
+	p->key_size_pow2 = rte_align64pow2(p->key_size);
+	if (p->key_size_pow2 < 8)
+		p->key_size_pow2 = 8;
+
+	p->key_size_log2 = __builtin_ctzll(p->key_size_pow2);
+
+	p->key_offset = params->key_offset;
+
+	/* Data. */
+	p->action_data_size = params->action_data_size;
+
+	p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
+
+	p->data_size_log2 = __builtin_ctzll(p->data_size_pow2);
+
+	/* Buckets. */
+	p->n_buckets = rte_align32pow2(params->n_keys_max);
+
+	p->bucket_mask = p->n_buckets - 1;
+
+	p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
+
+	p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
+					 p->bucket_key_all_size +
+					 TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
+
+	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
+
+	/* Timeout. */
+	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+
+	/* Total size. */
+	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
+
+	return 0;
+}
+
+static inline struct table_bucket *
+table_bucket_get(struct table *t, size_t bucket_id)
+{
+	return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
+}
+
+static inline uint8_t *
+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return &b->key[bucket_key_pos << t->params.key_size_log2];
+}
+
+static inline uint64_t *
+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return (uint64_t *)&b->key[t->params.bucket_key_all_size +
+				   (bucket_key_pos << t->params.data_size_log2)];
+}
+
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
+{
+	struct table_params p;
+	int status;
+
+	status = table_params_get(&p, params);
+
+	return status ? 0 : p.total_size;
+}
+
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
+{
+	struct table_params p;
+	struct table *t;
+	int status;
+
+	/* Check and process the input parameters. */
+	status = table_params_get(&p, params);
+	if (status)
+		return NULL;
+
+	/* Memory allocation. */
+	t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t)
+		return NULL;
+
+	/* Memory initialization. */
+	memcpy(&t->params, &p, sizeof(struct table_params));
+
+	if (params->key_mask0)
+		memcpy(t->key_mask0, params->key_mask0, params->key_size);
+	else
+		memset(t->key_mask0, 0xFF, params->key_size);
+
+	return t;
+}
+
+void
+rte_swx_table_learner_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	env_free(t, t->params.total_size);
+}
+
+struct mailbox {
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	struct table_bucket *bucket;
+
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	uint32_t input_sig;
+
+	/* Writer: lookup state 1. Reader(s): add(). */
+	uint8_t *input_key;
+
+	/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
+	uint32_t hit;
+
+	/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
+	size_t bucket_key_pos;
+
+	/* State. */
+	int state;
+};
+
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t input_time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+
+	switch (m->state) {
+	case 0: {
+		uint8_t *input_key;
+		struct table_bucket *b;
+		size_t bucket_id;
+		uint32_t input_sig;
+
+		input_key = &(*key)[t->params.key_offset];
+		input_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);
+		bucket_id = input_sig & t->params.bucket_mask;
+		b = table_bucket_get(t, bucket_id);
+
+		rte_prefetch0(b);
+		rte_prefetch0(&b->key[0]);
+		rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
+
+		m->bucket = b;
+		m->input_key = input_key;
+		m->input_sig = input_sig | 1;
+		m->state = 1;
+		return 0;
+	}
+
+	case 1: {
+		struct table_bucket *b = m->bucket;
+		uint32_t i;
+
+		/* Search the input key through the bucket keys. */
+		for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+			uint64_t time = b->time[i];
+			uint32_t sig = b->sig[i];
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint32_t key_size_pow2 = t->params.key_size_pow2;
+
+			time <<= 32;
+
+			if ((time > input_time) &&
+			    (sig == m->input_sig) &&
+			    table_keycmp(key, m->input_key, t->key_mask0, key_size_pow2)) {
+				uint64_t *data = table_bucket_data_get(t, b, i);
+
+				/* Hit. */
+				rte_prefetch0(data);
+
+				b->time[i] = (input_time + t->params.key_timeout) >> 32;
+
+				m->hit = 1;
+				m->bucket_key_pos = i;
+				m->state = 0;
+
+				*action_id = data[0];
+				*action_data = (uint8_t *)&data[1];
+				*hit = 1;
+				return 1;
+			}
+		}
+
+		/* Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+
+	default:
+		/* This state should never be reached. Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+}
+
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t input_time,
+			  uint64_t action_id,
+			  uint8_t *action_data)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b = m->bucket;
+	uint32_t i;
+
+	/* Lookup hit: The key, key signature and key time are already properly configured (the key
+	 * time was bumped by lookup), only the key data need to be updated.
+	 */
+	if (m->hit) {
+		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+
+		/* Install the key data. */
+		data[0] = action_id;
+		if (t->params.action_data_size && action_data)
+			memcpy(&data[1], action_data, t->params.action_data_size);
+
+		return 0;
+	}
+
+	/* Lookup miss: Search for a free position in the current bucket and install the key. */
+	for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+		uint64_t time = b->time[i];
+
+		time <<= 32;
+
+		/* Free position: Either there was never a key installed here, so the key time is
+		 * set to zero (the init value), which is always less than the current time, or this
+		 * position was used before, but the key expired (the key time is in the past).
+		 */
+		if (time < input_time) {
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint64_t *data = table_bucket_data_get(t, b, i);
+
+			/* Install the key. */
+			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			b->sig[i] = m->input_sig;
+			memcpy(key, m->input_key, t->params.key_size);
+
+			/* Install the key data. */
+			data[0] = action_id;
+			if (t->params.action_data_size && action_data)
+				memcpy(&data[1], action_data, t->params.action_data_size);
+
+			/* Mailbox. */
+			m->hit = 1;
+			m->bucket_key_pos = i;
+
+			return 0;
+		}
+	}
+
+	/* Bucket full. */
+	return 1;
+}
+
+void
+rte_swx_table_learner_delete(void *table __rte_unused,
+			     void *mailbox)
+{
+	struct mailbox *m = mailbox;
+
+	if (m->hit) {
+		struct table_bucket *b = m->bucket;
+
+		/* Expire the key. */
+		b->time[m->bucket_key_pos] = 0;
+
+		/* Mailbox. */
+		m->hit = 0;
+	}
+}
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
new file mode 100644
index 0000000000..d6ec733655
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Learner Table
+ *
+ * The learner table API.
+ *
+ * This table type is typically used for learning or connection tracking, where it allows for the
+ * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no
+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
+ * are automatically deleted from the table with no control plane intervention.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+/** Learner table creation parameters. */
+struct rte_swx_table_learner_params {
+	/** Key size in bytes. Must be non-zero. */
+	uint32_t key_size;
+
+	/** Offset of the first byte of the key within the key buffer. */
+	uint32_t key_offset;
+
+	/** Mask of *key_size* bytes logically laid over the bytes at positions
+	 * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify
+	 * which bits from the key buffer are part of the key and which ones are not. A bit value of
+	 * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while
+	 * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the
+	 * key, i.e. the *key_mask0* is an all-ones mask.
+	 */
+	uint8_t *key_mask0;
+
+	/** Maximum size (in bytes) of the action data. The data stored in the table for each entry
+	 * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.
+	 */
+	uint32_t action_data_size;
+
+	/** Maximum number of keys to be stored in the table together with their associated data. */
+	uint32_t n_keys_max;
+
+	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
+	 * deleted from the table after this many seconds.
+	 */
+	uint32_t key_timeout;
+};
+
+/**
+ * Learner table memory footprint get
+ *
+ * @param[in] params
+ *   Table create parameters.
+ * @return
+ *   Table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);
+
+/**
+ * Learner table mailbox size get
+ *
+ * The mailbox is used to store the context of a lookup operation that is in
+ * progress and it is passed as a parameter to the lookup operation. This allows
+ * for multiple concurrent lookup operations into the same table.
+ *
+ * @return
+ *   Table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void);
+
+/**
+ * Learner table create
+ *
+ * @param[in] params
+ *   Table creation parameters.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
+
+/**
+ * Learner table key lookup
+ *
+ * The table lookup operation searches a given key in the table and upon its completion it returns
+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case
+ * of lookup hit, the action_id and the action_data associated with the key are also returned.
+ *
+ * Multiple invocations of this function may be required in order to complete a single table lookup
+ * operation for a given table and a given lookup key. The completion of the table lookup operation
+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked
+ * again with exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and
+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent
+ * table key lookup and add operations into the same table.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current table lookup operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key
+ *   Lookup key. Its size must be equal to the table *key_size*.
+ * @param[out] action_id
+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid
+ *   when the function returns 1 and *hit* is set to true.
+ * @param[out] action_data
+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*
+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.
+ * @param[out] hit
+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero
+ *   (false) on table lookup miss.
+ * @return
+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation
+ *   is completed. No other return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit);
+
+/**
+ * Learner table key add
+ *
+ * This operation takes the latest key that was looked up in the table and adds it to the table with
+ * the given action ID and action data. Typically, this operation is only invoked when the latest
+ * lookup operation in the current table resulted in lookup miss.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[out] action_id
+ *   ID of the action associated with the key.
+ * @param[out] action_data
+ *   Action data for the *action_id* action.
+ * @return
+ *   0 on success, 1 or error (table full).
+ */
+__rte_experimental
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t time,
+			  uint64_t action_id,
+			  uint8_t *action_data);
+
+/**
+ * Learner table key delete
+ *
+ * This operation takes the latest key that was looked up in the table and deletes it from the
+ * table. Typically, this operation is only invoked to force the deletion of the key before the key
+ * expires on timeout due to inactivity.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_delete(void *table,
+			     void *mailbox);
+
+/**
+ * Learner table free
+ *
+ * @param[in] table
+ *   Table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index 29301480cb..f973a36ecc 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -36,4 +36,13 @@ EXPERIMENTAL {
 	rte_swx_table_selector_group_set;
 	rte_swx_table_selector_mailbox_size_get;
 	rte_swx_table_selector_select;
+
+	# added in 21.11
+	rte_swx_table_learner_add;
+	rte_swx_table_learner_create;
+	rte_swx_table_learner_delete;
+	rte_swx_table_learner_footprint_get;
+	rte_swx_table_learner_free;
+	rte_swx_table_learner_lookup;
+	rte_swx_table_learner_mailbox_size_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V4 2/4] pipeline: add support for learner tables
  2021-08-16 12:22     ` [dpdk-dev] [PATCH V4 1/4] table: add support learner tables Cristian Dumitrescu
@ 2021-08-16 12:22       ` Cristian Dumitrescu
  2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 3/4] examples/pipeline: " Cristian Dumitrescu
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-16 12:22 UTC (permalink / raw)
  To: dev

Add pipeline level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added more configuration consistency checks.
V3: Fixed one coding style indentation error.
V4: Fixed a pointer dereferencing issue in function rte_swx_ctl_pipeline_learner_stats_read().

 lib/pipeline/rte_swx_ctl.c           |  479 +++++++++++-
 lib/pipeline/rte_swx_ctl.h           |  185 +++++
 lib/pipeline/rte_swx_pipeline.c      | 1041 ++++++++++++++++++++++++--
 lib/pipeline/rte_swx_pipeline.h      |   77 ++
 lib/pipeline/rte_swx_pipeline_spec.c |  470 +++++++++++-
 lib/pipeline/version.map             |    8 +
 6 files changed, 2205 insertions(+), 55 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..6b5532fd3e 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;
@@ -746,7 +759,7 @@ struct action {
 	TAILQ_ENTRY(action) node;
 	char name[RTE_SWX_NAME_SIZE];
 	struct struct_type *st;
-	int *args_endianness; /* 0 = Host Byte Order (HBO). */
+	int *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */
 	struct instruction *instructions;
 	uint32_t n_instructions;
 	uint32_t id;
@@ -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,168 @@ 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
+action_has_nbo_args(struct action *a);
+
+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);
+	CHECK(!action_has_nbo_args(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 +7392,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 +8366,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 +9342,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,
 
@@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name)
 	return action_field_find(action, &name[2]);
 }
 
+static int
+action_has_nbo_args(struct action *a)
+{
+	uint32_t i;
+
+	/* Return if the action does not have any args. */
+	if (!a->st)
+		return 0; /* FALSE */
+
+	for (i = 0; i < a->st->n_fields; i++)
+		if (a->args_endianness[i])
+			return 1; /* TRUE */
+
+	return 0; /* FALSE */
+}
+
+static int
+action_does_learning(struct action *a)
+{
+	uint32_t i;
+
+	for (i = 0; i < a->n_instructions; i++)
+		switch (a->instructions[i].type) {
+		case INSTR_LEARNER_LEARN:
+			return 1; /* TRUE */
+
+		case INSTR_LEARNER_FORGET:
+			return 1; /* TRUE */
+
+		default:
+			continue;
+		}
+
+	return 0; /* FALSE */
+}
+
 int
 rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,
 			       const char *name,
@@ -9546,6 +9831,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);
 
@@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 		a = action_find(p, action_name);
 		CHECK(a, EINVAL);
+		CHECK(!action_does_learning(a), EINVAL);
 
 		action_data_size = a->st ? a->st->n_bits / 8 : 0;
 		if (action_data_size > action_data_size_max)
@@ -9964,6 +10251,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,73 +10509,604 @@ 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 (!ast) {
+		if (mf_name)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* 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);
+
+	/* 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);
+
+	/* 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];
+
+		CHECK(mf->n_bits == af->n_bits, EINVAL);
+	}
+
+	return 0;
+}
+
+static int
+learner_action_learning_check(struct rte_swx_pipeline *p,
+			      struct action *action,
+			      const char **action_names,
+			      uint32_t n_actions)
+{
+	uint32_t i;
+
+	/* For each "learn" instruction of the current action, check that the learned action (i.e.
+	 * the action passed as argument to the "learn" instruction) is also enabled for the
+	 * current learner table.
+	 */
+	for (i = 0; i < action->n_instructions; i++) {
+		struct instruction *instr = &action->instructions[i];
+		uint32_t found = 0, j;
+
+		if (instr->type != INSTR_LEARNER_LEARN)
+			continue;
+
+		for (j = 0; j < n_actions; j++) {
+			struct action *a;
+
+			a = action_find(p, action_names[j]);
+			if (!a)
+				return -EINVAL;
+
+			if (a->id == instr->learn.action_id)
+				found = 1;
+		}
+
+		if (!found)
+			return -EINVAL;
+	}
+
+	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;
+
+		status = learner_action_learning_check(p,
+						       a,
+						       params->action_names,
+						       params->n_actions);
+		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;
@@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p)
 			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 +11483,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 +11506,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 +11591,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 +11614,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 +11676,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 +11922,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 +12095,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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V4 3/4] examples/pipeline: add support for learner tables
  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       ` 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
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-16 12:22 UTC (permalink / raw)
  To: dev

Add application-level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 174 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index a29be05ef4..ad6e3db8d7 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1829,6 +1829,104 @@ cmd_pipeline_selector_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
 }
 
+static int
+pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				   const char *learner_name,
+				   FILE *file,
+				   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_learner_default_entry_read(p,
+									learner_name,
+									line,
+									&is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_default_entry_add(p,
+									learner_name,
+									entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_learner_default_help[] =
+"pipeline <pipeline_name> learner <learner_name> default <file_name>\n";
+
+static void
+cmd_pipeline_learner_default(char **tokens,
+			     uint32_t n_tokens,
+			     char *out,
+			     size_t out_size,
+			     void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *learner_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	learner_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_learner_default_entry_add(p->ctl,
+						    learner_name,
+						    file,
+						    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2503,6 +2601,64 @@ cmd_pipeline_stats(char **tokens,
 			out += strlen(out);
 		}
 	}
+
+	snprintf(out, out_size, "\nLearner tables:\n");
+	out_size -= strlen(out);
+	out += strlen(out);
+
+	for (i = 0; i < info.n_learners; i++) {
+		struct rte_swx_ctl_learner_info learner_info;
+		uint64_t n_pkts_action[info.n_actions];
+		struct rte_swx_learner_stats stats = {
+			.n_pkts_hit = 0,
+			.n_pkts_miss = 0,
+			.n_pkts_action = n_pkts_action,
+		};
+		uint32_t j;
+
+		status = rte_swx_ctl_learner_info_get(p->p, i, &learner_info);
+		if (status) {
+			snprintf(out, out_size, "Learner table info get error.");
+			return;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_stats_read(p->p, learner_info.name, &stats);
+		if (status) {
+			snprintf(out, out_size, "Learner table stats read error.");
+			return;
+		}
+
+		snprintf(out, out_size, "\tLearner table %s:\n"
+			"\t\tHit (packets): %" PRIu64 "\n"
+			"\t\tMiss (packets): %" PRIu64 "\n"
+			"\t\tLearn OK (packets): %" PRIu64 "\n"
+			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tForget (packets): %" PRIu64 "\n",
+			learner_info.name,
+			stats.n_pkts_hit,
+			stats.n_pkts_miss,
+			stats.n_pkts_learn_ok,
+			stats.n_pkts_learn_err,
+			stats.n_pkts_forget);
+		out_size -= strlen(out);
+		out += strlen(out);
+
+		for (j = 0; j < info.n_actions; j++) {
+			struct rte_swx_ctl_action_info action_info;
+
+			status = rte_swx_ctl_action_info_get(p->p, j, &action_info);
+			if (status) {
+				snprintf(out, out_size, "Action info get error.");
+				return;
+			}
+
+			snprintf(out, out_size, "\t\tAction %s (packets): %" PRIu64 "\n",
+				action_info.name,
+				stats.n_pkts_action[j]);
+			out_size -= strlen(out);
+			out += strlen(out);
+		}
+	}
 }
 
 static const char cmd_thread_pipeline_enable_help[] =
@@ -2634,6 +2790,7 @@ cmd_help(char **tokens,
 			"\tpipeline selector group member add\n"
 			"\tpipeline selector group member delete\n"
 			"\tpipeline selector show\n"
+			"\tpipeline learner default\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2783,6 +2940,15 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "learner") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_learner_default_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -3031,6 +3197,14 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "learner") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_learner_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V4 4/4] examples/pipeline: add learner table example
  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       ` Cristian Dumitrescu
  2021-09-20 15:01       ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Cristian Dumitrescu
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-08-16 12:22 UTC (permalink / raw)
  To: dev

Added the files to illustrate the learner table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added description to the .spec file.

 examples/pipeline/examples/learner.cli  |  37 +++++++
 examples/pipeline/examples/learner.spec | 127 ++++++++++++++++++++++++
 2 files changed, 164 insertions(+)
 create mode 100644 examples/pipeline/examples/learner.cli
 create mode 100644 examples/pipeline/examples/learner.spec

diff --git a/examples/pipeline/examples/learner.cli b/examples/pipeline/examples/learner.cli
new file mode 100644
index 0000000000..af7792624f
--- /dev/null
+++ b/examples/pipeline/examples/learner.cli
@@ -0,0 +1,37 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+;
+; Customize the LINK parameters to match your setup.
+;
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+;
+; PIPELINE0 setup.
+;
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/learner.spec
+
+;
+; Pipelines-to-threads mapping.
+;
+thread 1 pipeline PIPELINE0 enable
+
+; Once the application has started, the command to get the CLI prompt is: telnet 0.0.0.0 8086
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
new file mode 100644
index 0000000000..d635422282
--- /dev/null
+++ b/examples/pipeline/examples/learner.spec
@@ -0,0 +1,127 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; The learner tables are very useful for learning and connection tracking.
+;
+; As opposed to regular tables, which are read-only for the data plane, the learner tables can be
+; updated by the data plane without any control plane intervention. The "learning" process typically
+; takes place by having the default action (i.e. the table action which is executed on lookup miss)
+; explicitly add to the table with a specific action the key that just missed the lookup operation.
+; Each table key expires automatically after a configurable timeout period if not hit during this
+; interval.
+;
+; This example demonstrates a simple connection tracking setup, where the connections are identified
+; by the IPv4 destination address. The forwarding action assigned to each new connection gets the
+; output port as argument, with the output port of each connection generated by a counter that is
+; persistent between packets. On top of the usual table stats, the learner table stats include the
+; number of packets with learning related events.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+
+	// Arguments for the "fwd_action" action.
+	bit<32> fwd_action_arg_port_out
+}
+
+metadata instanceof metadata_t
+
+//
+// Registers.
+//
+regarray counter size 1 initval 0
+
+//
+// Actions
+//
+struct fwd_action_args_t {
+	bit<32> port_out
+}
+
+action fwd_action args instanceof fwd_action_args_t {
+	mov m.port_out t.port_out
+	return
+}
+
+action learn_action args none {
+	// Read current counter value into m.fwd_action_arg_port_out.
+	regrd m.fwd_action_arg_port_out counter 0
+
+	// Increment the counter.
+	regadd counter 0 1
+
+	// Limit the output port values to 0 .. 3.
+	and m.fwd_action_arg_port_out 3
+
+	// Add the current lookup key to the table with fwd_action as the key action. The action
+	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
+	// packet meta-data fields have to be written before the "learn" instruction is invoked.
+	learn fwd_action
+
+	// Send the current packet to the same output port.
+	mov m.port_out m.fwd_action_arg_port_out
+
+	return
+}
+
+//
+// Tables.
+//
+learner fwd_table {
+	key {
+		h.ipv4.dst_addr
+	}
+
+	actions {
+		fwd_action args m.fwd_action_arg_port_out
+
+		learn_action args none
+	}
+
+	default_action learn_action args none
+
+	size 1048576
+
+	timeout 120
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	table fwd_table
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V5 1/4] table: add support learner tables
  2021-08-16 12:22     ` [dpdk-dev] [PATCH V4 1/4] table: add support learner tables Cristian Dumitrescu
                         ` (2 preceding siblings ...)
  2021-08-16 12:22       ` [dpdk-dev] [PATCH V4 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
@ 2021-09-20 15:01       ` Cristian Dumitrescu
  2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 2/4] pipeline: add support for " Cristian Dumitrescu
                           ` (3 more replies)
  3 siblings, 4 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-09-20 15:01 UTC (permalink / raw)
  To: dev

A learner table is typically used for learning or connection tracking,
where it allows for the implementation of the "add on miss" scenario:
whenever the lookup key is not found in the table (lookup miss), the
data plane can decide to add this key to the table with a given action
with no control plane intervention. Likewise, the table keys expire
based on a configurable timeout and are automatically deleted from the
table with no control plane intervention.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-18023 ("[V2,1/5] pipeline: prepare for variable size headers")

V2: fixed one "line too long" coding style warning.

 lib/table/meson.build             |   2 +
 lib/table/rte_swx_table_learner.c | 617 ++++++++++++++++++++++++++++++
 lib/table/rte_swx_table_learner.h | 206 ++++++++++
 lib/table/version.map             |   9 +
 4 files changed, 834 insertions(+)
 create mode 100644 lib/table/rte_swx_table_learner.c
 create mode 100644 lib/table/rte_swx_table_learner.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index a1384456a9..ac1f1aac27 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+        'rte_swx_table_learner.c',
         'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
@@ -21,6 +22,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+        'rte_swx_table_learner.h',
         'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
new file mode 100644
index 0000000000..c3c840ff06
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.c
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_learner.h"
+
+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
+#endif
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+/*
+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
+ */
+static inline uint32_t
+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
+{
+	uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;
+
+	switch (n_bytes) {
+	case 8: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint32_t result = 1;
+
+		if (xor0)
+			result = 0;
+		return result;
+	}
+
+	case 16: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t or = xor0 | xor1;
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 32: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t or = (xor0 | xor1) | (xor2 | xor3);
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	case 64: {
+		uint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);
+		uint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);
+		uint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);
+		uint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);
+		uint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);
+		uint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);
+		uint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);
+		uint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);
+		uint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |
+			      ((xor4 | xor5) | (xor6 | xor7));
+		uint32_t result = 1;
+
+		if (or)
+			result = 0;
+		return result;
+	}
+
+	default: {
+		uint32_t i;
+
+		for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+			if (a64[i] != (b64[i] & b_mask64[i]))
+				return 0;
+		return 1;
+	}
+	}
+}
+
+#define TABLE_KEYS_PER_BUCKET 4
+
+#define TABLE_BUCKET_PAD_SIZE \
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+
+struct table_bucket {
+	uint32_t time[TABLE_KEYS_PER_BUCKET];
+	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
+	uint8_t key[0];
+};
+
+struct table_params {
+	/* The real key size. Must be non-zero. */
+	size_t key_size;
+
+	/* They key size upgrated to the next power of 2. This used for hash generation (in
+	 * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why
+	 * key sizes bigger than 64 bytes are not allowed.
+	 */
+	size_t key_size_pow2;
+
+	/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
+	size_t key_size_log2;
+
+	/* The key offset within the key buffer. */
+	size_t key_offset;
+
+	/* The real action data size. */
+	size_t action_data_size;
+
+	/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
+	 * next power of 2.
+	 */
+	size_t data_size_pow2;
+
+	/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t data_size_log2;
+
+	/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
+	size_t n_buckets;
+
+	/* Bucket mask. Purpose: replace modulo with bitmask and operation. */
+	size_t bucket_mask;
+
+	/* Total number of key bytes in the bucket, including the key padding bytes. There are
+	 * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
+	 */
+	size_t bucket_key_all_size;
+
+	/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
+	size_t bucket_size;
+
+	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
+	size_t bucket_size_log2;
+
+	/* Timeout in CPU clock cycles. */
+	uint64_t key_timeout;
+
+	/* Total memory size. */
+	size_t total_size;
+};
+
+struct table {
+	/* Table parameters. */
+	struct table_params params;
+
+	/* Key mask. Array of *key_size* bytes. */
+	uint8_t key_mask0[RTE_CACHE_LINE_SIZE];
+
+	/* Table buckets. */
+	uint8_t buckets[0];
+} __rte_cache_aligned;
+
+static int
+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
+{
+	/* Check input parameters. */
+	if (!params ||
+	    !params->key_size ||
+	    (params->key_size > 64) ||
+	    !params->n_keys_max ||
+	    (params->n_keys_max > 1U << 31) ||
+	    !params->key_timeout)
+		return -EINVAL;
+
+	/* Key. */
+	p->key_size = params->key_size;
+
+	p->key_size_pow2 = rte_align64pow2(p->key_size);
+	if (p->key_size_pow2 < 8)
+		p->key_size_pow2 = 8;
+
+	p->key_size_log2 = __builtin_ctzll(p->key_size_pow2);
+
+	p->key_offset = params->key_offset;
+
+	/* Data. */
+	p->action_data_size = params->action_data_size;
+
+	p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
+
+	p->data_size_log2 = __builtin_ctzll(p->data_size_pow2);
+
+	/* Buckets. */
+	p->n_buckets = rte_align32pow2(params->n_keys_max);
+
+	p->bucket_mask = p->n_buckets - 1;
+
+	p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
+
+	p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
+					 p->bucket_key_all_size +
+					 TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
+
+	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
+
+	/* Timeout. */
+	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+
+	/* Total size. */
+	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
+
+	return 0;
+}
+
+static inline struct table_bucket *
+table_bucket_get(struct table *t, size_t bucket_id)
+{
+	return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
+}
+
+static inline uint8_t *
+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return &b->key[bucket_key_pos << t->params.key_size_log2];
+}
+
+static inline uint64_t *
+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
+{
+	return (uint64_t *)&b->key[t->params.bucket_key_all_size +
+				   (bucket_key_pos << t->params.data_size_log2)];
+}
+
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
+{
+	struct table_params p;
+	int status;
+
+	status = table_params_get(&p, params);
+
+	return status ? 0 : p.total_size;
+}
+
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
+{
+	struct table_params p;
+	struct table *t;
+	int status;
+
+	/* Check and process the input parameters. */
+	status = table_params_get(&p, params);
+	if (status)
+		return NULL;
+
+	/* Memory allocation. */
+	t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t)
+		return NULL;
+
+	/* Memory initialization. */
+	memcpy(&t->params, &p, sizeof(struct table_params));
+
+	if (params->key_mask0)
+		memcpy(t->key_mask0, params->key_mask0, params->key_size);
+	else
+		memset(t->key_mask0, 0xFF, params->key_size);
+
+	return t;
+}
+
+void
+rte_swx_table_learner_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	env_free(t, t->params.total_size);
+}
+
+struct mailbox {
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	struct table_bucket *bucket;
+
+	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
+	uint32_t input_sig;
+
+	/* Writer: lookup state 1. Reader(s): add(). */
+	uint8_t *input_key;
+
+	/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
+	uint32_t hit;
+
+	/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
+	size_t bucket_key_pos;
+
+	/* State. */
+	int state;
+};
+
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t input_time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+
+	switch (m->state) {
+	case 0: {
+		uint8_t *input_key;
+		struct table_bucket *b;
+		size_t bucket_id;
+		uint32_t input_sig;
+
+		input_key = &(*key)[t->params.key_offset];
+		input_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);
+		bucket_id = input_sig & t->params.bucket_mask;
+		b = table_bucket_get(t, bucket_id);
+
+		rte_prefetch0(b);
+		rte_prefetch0(&b->key[0]);
+		rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
+
+		m->bucket = b;
+		m->input_key = input_key;
+		m->input_sig = input_sig | 1;
+		m->state = 1;
+		return 0;
+	}
+
+	case 1: {
+		struct table_bucket *b = m->bucket;
+		uint32_t i;
+
+		/* Search the input key through the bucket keys. */
+		for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+			uint64_t time = b->time[i];
+			uint32_t sig = b->sig[i];
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint32_t key_size_pow2 = t->params.key_size_pow2;
+
+			time <<= 32;
+
+			if ((time > input_time) &&
+			    (sig == m->input_sig) &&
+			    table_keycmp(key, m->input_key, t->key_mask0, key_size_pow2)) {
+				uint64_t *data = table_bucket_data_get(t, b, i);
+
+				/* Hit. */
+				rte_prefetch0(data);
+
+				b->time[i] = (input_time + t->params.key_timeout) >> 32;
+
+				m->hit = 1;
+				m->bucket_key_pos = i;
+				m->state = 0;
+
+				*action_id = data[0];
+				*action_data = (uint8_t *)&data[1];
+				*hit = 1;
+				return 1;
+			}
+		}
+
+		/* Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+
+	default:
+		/* This state should never be reached. Miss. */
+		m->hit = 0;
+		m->state = 0;
+
+		*hit = 0;
+		return 1;
+	}
+}
+
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t input_time,
+			  uint64_t action_id,
+			  uint8_t *action_data)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b = m->bucket;
+	uint32_t i;
+
+	/* Lookup hit: The key, key signature and key time are already properly configured (the key
+	 * time was bumped by lookup), only the key data need to be updated.
+	 */
+	if (m->hit) {
+		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+
+		/* Install the key data. */
+		data[0] = action_id;
+		if (t->params.action_data_size && action_data)
+			memcpy(&data[1], action_data, t->params.action_data_size);
+
+		return 0;
+	}
+
+	/* Lookup miss: Search for a free position in the current bucket and install the key. */
+	for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
+		uint64_t time = b->time[i];
+
+		time <<= 32;
+
+		/* Free position: Either there was never a key installed here, so the key time is
+		 * set to zero (the init value), which is always less than the current time, or this
+		 * position was used before, but the key expired (the key time is in the past).
+		 */
+		if (time < input_time) {
+			uint8_t *key = table_bucket_key_get(t, b, i);
+			uint64_t *data = table_bucket_data_get(t, b, i);
+
+			/* Install the key. */
+			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			b->sig[i] = m->input_sig;
+			memcpy(key, m->input_key, t->params.key_size);
+
+			/* Install the key data. */
+			data[0] = action_id;
+			if (t->params.action_data_size && action_data)
+				memcpy(&data[1], action_data, t->params.action_data_size);
+
+			/* Mailbox. */
+			m->hit = 1;
+			m->bucket_key_pos = i;
+
+			return 0;
+		}
+	}
+
+	/* Bucket full. */
+	return 1;
+}
+
+void
+rte_swx_table_learner_delete(void *table __rte_unused,
+			     void *mailbox)
+{
+	struct mailbox *m = mailbox;
+
+	if (m->hit) {
+		struct table_bucket *b = m->bucket;
+
+		/* Expire the key. */
+		b->time[m->bucket_key_pos] = 0;
+
+		/* Mailbox. */
+		m->hit = 0;
+	}
+}
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
new file mode 100644
index 0000000000..d6ec733655
--- /dev/null
+++ b/lib/table/rte_swx_table_learner.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Learner Table
+ *
+ * The learner table API.
+ *
+ * This table type is typically used for learning or connection tracking, where it allows for the
+ * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no
+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
+ * are automatically deleted from the table with no control plane intervention.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+/** Learner table creation parameters. */
+struct rte_swx_table_learner_params {
+	/** Key size in bytes. Must be non-zero. */
+	uint32_t key_size;
+
+	/** Offset of the first byte of the key within the key buffer. */
+	uint32_t key_offset;
+
+	/** Mask of *key_size* bytes logically laid over the bytes at positions
+	 * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify
+	 * which bits from the key buffer are part of the key and which ones are not. A bit value of
+	 * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while
+	 * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the
+	 * key, i.e. the *key_mask0* is an all-ones mask.
+	 */
+	uint8_t *key_mask0;
+
+	/** Maximum size (in bytes) of the action data. The data stored in the table for each entry
+	 * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.
+	 */
+	uint32_t action_data_size;
+
+	/** Maximum number of keys to be stored in the table together with their associated data. */
+	uint32_t n_keys_max;
+
+	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
+	 * deleted from the table after this many seconds.
+	 */
+	uint32_t key_timeout;
+};
+
+/**
+ * Learner table memory footprint get
+ *
+ * @param[in] params
+ *   Table create parameters.
+ * @return
+ *   Table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);
+
+/**
+ * Learner table mailbox size get
+ *
+ * The mailbox is used to store the context of a lookup operation that is in
+ * progress and it is passed as a parameter to the lookup operation. This allows
+ * for multiple concurrent lookup operations into the same table.
+ *
+ * @return
+ *   Table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_learner_mailbox_size_get(void);
+
+/**
+ * Learner table create
+ *
+ * @param[in] params
+ *   Table creation parameters.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
+
+/**
+ * Learner table key lookup
+ *
+ * The table lookup operation searches a given key in the table and upon its completion it returns
+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case
+ * of lookup hit, the action_id and the action_data associated with the key are also returned.
+ *
+ * Multiple invocations of this function may be required in order to complete a single table lookup
+ * operation for a given table and a given lookup key. The completion of the table lookup operation
+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked
+ * again with exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and
+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent
+ * table key lookup and add operations into the same table.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current table lookup operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key
+ *   Lookup key. Its size must be equal to the table *key_size*.
+ * @param[out] action_id
+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid
+ *   when the function returns 1 and *hit* is set to true.
+ * @param[out] action_data
+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*
+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.
+ * @param[out] hit
+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero
+ *   (false) on table lookup miss.
+ * @return
+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation
+ *   is completed. No other return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_learner_lookup(void *table,
+			     void *mailbox,
+			     uint64_t time,
+			     uint8_t **key,
+			     uint64_t *action_id,
+			     uint8_t **action_data,
+			     int *hit);
+
+/**
+ * Learner table key add
+ *
+ * This operation takes the latest key that was looked up in the table and adds it to the table with
+ * the given action ID and action data. Typically, this operation is only invoked when the latest
+ * lookup operation in the current table resulted in lookup miss.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[out] action_id
+ *   ID of the action associated with the key.
+ * @param[out] action_data
+ *   Action data for the *action_id* action.
+ * @return
+ *   0 on success, 1 or error (table full).
+ */
+__rte_experimental
+uint32_t
+rte_swx_table_learner_add(void *table,
+			  void *mailbox,
+			  uint64_t time,
+			  uint64_t action_id,
+			  uint8_t *action_data);
+
+/**
+ * Learner table key delete
+ *
+ * This operation takes the latest key that was looked up in the table and deletes it from the
+ * table. Typically, this operation is only invoked to force the deletion of the key before the key
+ * expires on timeout due to inactivity.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_delete(void *table,
+			     void *mailbox);
+
+/**
+ * Learner table free
+ *
+ * @param[in] table
+ *   Table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index 65f9645d25..efe5f6e52c 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -36,4 +36,13 @@ EXPERIMENTAL {
 	rte_swx_table_selector_group_set;
 	rte_swx_table_selector_mailbox_size_get;
 	rte_swx_table_selector_select;
+
+	# added in 21.11
+	rte_swx_table_learner_add;
+	rte_swx_table_learner_create;
+	rte_swx_table_learner_delete;
+	rte_swx_table_learner_footprint_get;
+	rte_swx_table_learner_free;
+	rte_swx_table_learner_lookup;
+	rte_swx_table_learner_mailbox_size_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V5 2/4] pipeline: add support for learner tables
  2021-09-20 15:01       ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Cristian Dumitrescu
@ 2021-09-20 15:01         ` Cristian Dumitrescu
  2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 3/4] examples/pipeline: " Cristian Dumitrescu
                           ` (2 subsequent siblings)
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-09-20 15:01 UTC (permalink / raw)
  To: dev

Add pipeline level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added more configuration consistency checks.
V3: Fixed one coding style indentation error.
V4: Fixed a pointer dereferencing issue in function rte_swx_ctl_pipeline_learner_stats_read().
V5: Added function rte_swx_ctl_pipeline_learner_default_entry_read() to the version.map file.

 lib/pipeline/rte_swx_ctl.c           |  479 +++++++++++-
 lib/pipeline/rte_swx_ctl.h           |  186 +++++
 lib/pipeline/rte_swx_pipeline.c      | 1041 ++++++++++++++++++++++++--
 lib/pipeline/rte_swx_pipeline.h      |   77 ++
 lib/pipeline/rte_swx_pipeline_spec.c |  470 +++++++++++-
 lib/pipeline/version.map             |    9 +
 6 files changed, 2207 insertions(+), 55 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..807597229d 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,32 @@ 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.
+ */
+__rte_experimental
+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 96786fb9a0..f89a134a52 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;
@@ -746,7 +759,7 @@ struct action {
 	TAILQ_ENTRY(action) node;
 	char name[RTE_SWX_NAME_SIZE];
 	struct struct_type *st;
-	int *args_endianness; /* 0 = Host Byte Order (HBO). */
+	int *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */
 	struct instruction *instructions;
 	uint32_t n_instructions;
 	uint32_t id;
@@ -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,168 @@ 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
+action_has_nbo_args(struct action *a);
+
+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);
+	CHECK(!action_has_nbo_args(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 +7392,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 +8366,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 +9342,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,
 
@@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name)
 	return action_field_find(action, &name[2]);
 }
 
+static int
+action_has_nbo_args(struct action *a)
+{
+	uint32_t i;
+
+	/* Return if the action does not have any args. */
+	if (!a->st)
+		return 0; /* FALSE */
+
+	for (i = 0; i < a->st->n_fields; i++)
+		if (a->args_endianness[i])
+			return 1; /* TRUE */
+
+	return 0; /* FALSE */
+}
+
+static int
+action_does_learning(struct action *a)
+{
+	uint32_t i;
+
+	for (i = 0; i < a->n_instructions; i++)
+		switch (a->instructions[i].type) {
+		case INSTR_LEARNER_LEARN:
+			return 1; /* TRUE */
+
+		case INSTR_LEARNER_FORGET:
+			return 1; /* TRUE */
+
+		default:
+			continue;
+		}
+
+	return 0; /* FALSE */
+}
+
 int
 rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,
 			       const char *name,
@@ -9546,6 +9831,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);
 
@@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 		a = action_find(p, action_name);
 		CHECK(a, EINVAL);
+		CHECK(!action_does_learning(a), EINVAL);
 
 		action_data_size = a->st ? a->st->n_bits / 8 : 0;
 		if (action_data_size > action_data_size_max)
@@ -9964,6 +10251,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,73 +10509,604 @@ 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 (!ast) {
+		if (mf_name)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* 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);
+
+	/* 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);
+
+	/* 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];
+
+		CHECK(mf->n_bits == af->n_bits, EINVAL);
+	}
+
+	return 0;
+}
+
+static int
+learner_action_learning_check(struct rte_swx_pipeline *p,
+			      struct action *action,
+			      const char **action_names,
+			      uint32_t n_actions)
+{
+	uint32_t i;
+
+	/* For each "learn" instruction of the current action, check that the learned action (i.e.
+	 * the action passed as argument to the "learn" instruction) is also enabled for the
+	 * current learner table.
+	 */
+	for (i = 0; i < action->n_instructions; i++) {
+		struct instruction *instr = &action->instructions[i];
+		uint32_t found = 0, j;
+
+		if (instr->type != INSTR_LEARNER_LEARN)
+			continue;
+
+		for (j = 0; j < n_actions; j++) {
+			struct action *a;
+
+			a = action_find(p, action_names[j]);
+			if (!a)
+				return -EINVAL;
+
+			if (a->id == instr->learn.action_id)
+				found = 1;
+		}
+
+		if (!found)
+			return -EINVAL;
+	}
+
+	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;
+
+		status = learner_action_learning_check(p,
+						       a,
+						       params->action_names,
+						       params->n_actions);
+		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;
@@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p)
 			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 +11483,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 +11506,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 +11591,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 +11614,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 +11676,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 +11922,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 +12095,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 2b68f584a4..8bc90e7cd7 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -129,4 +129,13 @@ 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_default_entry_read;
+	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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V5 3/4] examples/pipeline: add support for learner tables
  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         ` 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
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-09-20 15:01 UTC (permalink / raw)
  To: dev

Add application-level support for learner tables.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 174 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 1e2dd9d704..39b1e7a41b 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1829,6 +1829,104 @@ cmd_pipeline_selector_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
 }
 
+static int
+pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				   const char *learner_name,
+				   FILE *file,
+				   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_learner_default_entry_read(p,
+									learner_name,
+									line,
+									&is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_default_entry_add(p,
+									learner_name,
+									entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_learner_default_help[] =
+"pipeline <pipeline_name> learner <learner_name> default <file_name>\n";
+
+static void
+cmd_pipeline_learner_default(char **tokens,
+			     uint32_t n_tokens,
+			     char *out,
+			     size_t out_size,
+			     void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *learner_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	learner_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_learner_default_entry_add(p->ctl,
+						    learner_name,
+						    file,
+						    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2503,6 +2601,64 @@ cmd_pipeline_stats(char **tokens,
 			out += strlen(out);
 		}
 	}
+
+	snprintf(out, out_size, "\nLearner tables:\n");
+	out_size -= strlen(out);
+	out += strlen(out);
+
+	for (i = 0; i < info.n_learners; i++) {
+		struct rte_swx_ctl_learner_info learner_info;
+		uint64_t n_pkts_action[info.n_actions];
+		struct rte_swx_learner_stats stats = {
+			.n_pkts_hit = 0,
+			.n_pkts_miss = 0,
+			.n_pkts_action = n_pkts_action,
+		};
+		uint32_t j;
+
+		status = rte_swx_ctl_learner_info_get(p->p, i, &learner_info);
+		if (status) {
+			snprintf(out, out_size, "Learner table info get error.");
+			return;
+		}
+
+		status = rte_swx_ctl_pipeline_learner_stats_read(p->p, learner_info.name, &stats);
+		if (status) {
+			snprintf(out, out_size, "Learner table stats read error.");
+			return;
+		}
+
+		snprintf(out, out_size, "\tLearner table %s:\n"
+			"\t\tHit (packets): %" PRIu64 "\n"
+			"\t\tMiss (packets): %" PRIu64 "\n"
+			"\t\tLearn OK (packets): %" PRIu64 "\n"
+			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tForget (packets): %" PRIu64 "\n",
+			learner_info.name,
+			stats.n_pkts_hit,
+			stats.n_pkts_miss,
+			stats.n_pkts_learn_ok,
+			stats.n_pkts_learn_err,
+			stats.n_pkts_forget);
+		out_size -= strlen(out);
+		out += strlen(out);
+
+		for (j = 0; j < info.n_actions; j++) {
+			struct rte_swx_ctl_action_info action_info;
+
+			status = rte_swx_ctl_action_info_get(p->p, j, &action_info);
+			if (status) {
+				snprintf(out, out_size, "Action info get error.");
+				return;
+			}
+
+			snprintf(out, out_size, "\t\tAction %s (packets): %" PRIu64 "\n",
+				action_info.name,
+				stats.n_pkts_action[j]);
+			out_size -= strlen(out);
+			out += strlen(out);
+		}
+	}
 }
 
 static const char cmd_thread_pipeline_enable_help[] =
@@ -2634,6 +2790,7 @@ cmd_help(char **tokens,
 			"\tpipeline selector group member add\n"
 			"\tpipeline selector group member delete\n"
 			"\tpipeline selector show\n"
+			"\tpipeline learner default\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2783,6 +2940,15 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "learner") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_learner_default_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -3031,6 +3197,14 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "learner") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_learner_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [dpdk-dev] [PATCH V5 4/4] examples/pipeline: add learner table example
  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         ` Cristian Dumitrescu
  2021-09-27  7:53         ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Thomas Monjalon
  3 siblings, 0 replies; 21+ messages in thread
From: Cristian Dumitrescu @ 2021-09-20 15:01 UTC (permalink / raw)
  To: dev

Added the files to illustrate the learner table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---

V2: Added description to the .spec file.

 examples/pipeline/examples/learner.cli  |  37 +++++++
 examples/pipeline/examples/learner.spec | 127 ++++++++++++++++++++++++
 2 files changed, 164 insertions(+)
 create mode 100644 examples/pipeline/examples/learner.cli
 create mode 100644 examples/pipeline/examples/learner.spec

diff --git a/examples/pipeline/examples/learner.cli b/examples/pipeline/examples/learner.cli
new file mode 100644
index 0000000000..af7792624f
--- /dev/null
+++ b/examples/pipeline/examples/learner.cli
@@ -0,0 +1,37 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+;
+; Customize the LINK parameters to match your setup.
+;
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+;
+; PIPELINE0 setup.
+;
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/learner.spec
+
+;
+; Pipelines-to-threads mapping.
+;
+thread 1 pipeline PIPELINE0 enable
+
+; Once the application has started, the command to get the CLI prompt is: telnet 0.0.0.0 8086
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
new file mode 100644
index 0000000000..d635422282
--- /dev/null
+++ b/examples/pipeline/examples/learner.spec
@@ -0,0 +1,127 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; The learner tables are very useful for learning and connection tracking.
+;
+; As opposed to regular tables, which are read-only for the data plane, the learner tables can be
+; updated by the data plane without any control plane intervention. The "learning" process typically
+; takes place by having the default action (i.e. the table action which is executed on lookup miss)
+; explicitly add to the table with a specific action the key that just missed the lookup operation.
+; Each table key expires automatically after a configurable timeout period if not hit during this
+; interval.
+;
+; This example demonstrates a simple connection tracking setup, where the connections are identified
+; by the IPv4 destination address. The forwarding action assigned to each new connection gets the
+; output port as argument, with the output port of each connection generated by a counter that is
+; persistent between packets. On top of the usual table stats, the learner table stats include the
+; number of packets with learning related events.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+
+	// Arguments for the "fwd_action" action.
+	bit<32> fwd_action_arg_port_out
+}
+
+metadata instanceof metadata_t
+
+//
+// Registers.
+//
+regarray counter size 1 initval 0
+
+//
+// Actions
+//
+struct fwd_action_args_t {
+	bit<32> port_out
+}
+
+action fwd_action args instanceof fwd_action_args_t {
+	mov m.port_out t.port_out
+	return
+}
+
+action learn_action args none {
+	// Read current counter value into m.fwd_action_arg_port_out.
+	regrd m.fwd_action_arg_port_out counter 0
+
+	// Increment the counter.
+	regadd counter 0 1
+
+	// Limit the output port values to 0 .. 3.
+	and m.fwd_action_arg_port_out 3
+
+	// Add the current lookup key to the table with fwd_action as the key action. The action
+	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
+	// packet meta-data fields have to be written before the "learn" instruction is invoked.
+	learn fwd_action
+
+	// Send the current packet to the same output port.
+	mov m.port_out m.fwd_action_arg_port_out
+
+	return
+}
+
+//
+// Tables.
+//
+learner fwd_table {
+	key {
+		h.ipv4.dst_addr
+	}
+
+	actions {
+		fwd_action args m.fwd_action_arg_port_out
+
+		learn_action args none
+	}
+
+	default_action learn_action args none
+
+	size 1048576
+
+	timeout 120
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	table fwd_table
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [dpdk-dev] [PATCH V5 1/4] table: add support learner tables
  2021-09-20 15:01       ` [dpdk-dev] [PATCH V5 1/4] table: add support learner tables Cristian Dumitrescu
                           ` (2 preceding siblings ...)
  2021-09-20 15:01         ` [dpdk-dev] [PATCH V5 4/4] examples/pipeline: add learner table example Cristian Dumitrescu
@ 2021-09-27  7:53         ` Thomas Monjalon
  3 siblings, 0 replies; 21+ messages in thread
From: Thomas Monjalon @ 2021-09-27  7:53 UTC (permalink / raw)
  To: Cristian Dumitrescu; +Cc: dev

20/09/2021 17:01, Cristian Dumitrescu:
> A learner table is typically used for learning or connection tracking,
> where it allows for the implementation of the "add on miss" scenario:
> whenever the lookup key is not found in the table (lookup miss), the
> data plane can decide to add this key to the table with a given action
> with no control plane intervention. Likewise, the table keys expire
> based on a configurable timeout and are automatically deleted from the
> table with no control plane intervention.
> 
> Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>

Series applied, thanks.




^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2021-09-27  7:53 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-13 23:52 [dpdk-dev] [PATCH 1/4] table: add support learner tables Cristian Dumitrescu
2021-08-13 23:52 ` [dpdk-dev] [PATCH 2/4] pipeline: add support for " Cristian Dumitrescu
2021-08-13 23:52 ` [dpdk-dev] [PATCH 3/4] examples/pipeline: " 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

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.