* [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(¶ms->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(®array_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(®array_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(¶ms->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(®array_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(®array_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(¶ms->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(®array_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(®array_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(¶ms->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(®array_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(®array_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(¶ms->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(®array_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(®array_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).