DPDK patches and discussions
 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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).