* [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).