get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/99323/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 99323,
    "url": "http://patches.dpdk.org/api/patches/99323/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20210920150133.84928-1-cristian.dumitrescu@intel.com/",
    "project": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20210920150133.84928-1-cristian.dumitrescu@intel.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20210920150133.84928-1-cristian.dumitrescu@intel.com",
    "date": "2021-09-20T15:01:30",
    "name": "[V5,1/4] table: add support learner tables",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": true,
    "hash": "7143e0d9c3104e4b766dec1cfe10e8b5f3ad77e3",
    "submitter": {
        "id": 19,
        "url": "http://patches.dpdk.org/api/people/19/?format=api",
        "name": "Cristian Dumitrescu",
        "email": "cristian.dumitrescu@intel.com"
    },
    "delegate": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20210920150133.84928-1-cristian.dumitrescu@intel.com/mbox/",
    "series": [
        {
            "id": 19048,
            "url": "http://patches.dpdk.org/api/series/19048/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=19048",
            "date": "2021-09-20T15:01:30",
            "name": "[V5,1/4] table: add support learner tables",
            "version": 5,
            "mbox": "http://patches.dpdk.org/series/19048/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/99323/comments/",
    "check": "fail",
    "checks": "http://patches.dpdk.org/api/patches/99323/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 6B8BEA0548;\n\tMon, 20 Sep 2021 17:01:40 +0200 (CEST)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id DCD26410E1;\n\tMon, 20 Sep 2021 17:01:39 +0200 (CEST)",
            "from mga01.intel.com (mga01.intel.com [192.55.52.88])\n by mails.dpdk.org (Postfix) with ESMTP id 878DC40DF5\n for <dev@dpdk.org>; Mon, 20 Sep 2021 17:01:37 +0200 (CEST)",
            "from orsmga003.jf.intel.com ([10.7.209.27])\n by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 20 Sep 2021 08:01:36 -0700",
            "from silpixa00400573.ir.intel.com (HELO\n silpixa00400573.ger.corp.intel.com) ([10.237.223.107])\n by orsmga003.jf.intel.com with ESMTP; 20 Sep 2021 08:01:34 -0700"
        ],
        "X-IronPort-AV": [
            "E=McAfee;i=\"6200,9189,10113\"; a=\"245557174\"",
            "E=Sophos;i=\"5.85,308,1624345200\"; d=\"scan'208\";a=\"245557174\"",
            "E=Sophos;i=\"5.85,308,1624345200\"; d=\"scan'208\";a=\"435552153\""
        ],
        "X-ExtLoop1": "1",
        "From": "Cristian Dumitrescu <cristian.dumitrescu@intel.com>",
        "To": "dev@dpdk.org",
        "Date": "Mon, 20 Sep 2021 16:01:30 +0100",
        "Message-Id": "<20210920150133.84928-1-cristian.dumitrescu@intel.com>",
        "X-Mailer": "git-send-email 2.17.1",
        "In-Reply-To": "<20210816122257.84651-1-cristian.dumitrescu@intel.com>",
        "References": "<20210816122257.84651-1-cristian.dumitrescu@intel.com>",
        "Subject": "[dpdk-dev] [PATCH V5 1/4] table: add support learner tables",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org",
        "Sender": "\"dev\" <dev-bounces@dpdk.org>"
    },
    "content": "A learner table is typically used for learning or connection tracking,\nwhere it allows for the implementation of the \"add on miss\" scenario:\nwhenever the lookup key is not found in the table (lookup miss), the\ndata plane can decide to add this key to the table with a given action\nwith no control plane intervention. Likewise, the table keys expire\nbased on a configurable timeout and are automatically deleted from the\ntable with no control plane intervention.\n\nSigned-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>\n---\nDepends-on: series-18023 (\"[V2,1/5] pipeline: prepare for variable size headers\")\n\nV2: fixed one \"line too long\" coding style warning.\n\n lib/table/meson.build             |   2 +\n lib/table/rte_swx_table_learner.c | 617 ++++++++++++++++++++++++++++++\n lib/table/rte_swx_table_learner.h | 206 ++++++++++\n lib/table/version.map             |   9 +\n 4 files changed, 834 insertions(+)\n create mode 100644 lib/table/rte_swx_table_learner.c\n create mode 100644 lib/table/rte_swx_table_learner.h",
    "diff": "diff --git a/lib/table/meson.build b/lib/table/meson.build\nindex a1384456a9..ac1f1aac27 100644\n--- a/lib/table/meson.build\n+++ b/lib/table/meson.build\n@@ -3,6 +3,7 @@\n \n sources = files(\n         'rte_swx_table_em.c',\n+        'rte_swx_table_learner.c',\n         'rte_swx_table_selector.c',\n         'rte_swx_table_wm.c',\n         'rte_table_acl.c',\n@@ -21,6 +22,7 @@ headers = files(\n         'rte_lru.h',\n         'rte_swx_table.h',\n         'rte_swx_table_em.h',\n+        'rte_swx_table_learner.h',\n         'rte_swx_table_selector.h',\n         'rte_swx_table_wm.h',\n         'rte_table.h',\ndiff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c\nnew file mode 100644\nindex 0000000000..c3c840ff06\n--- /dev/null\n+++ b/lib/table/rte_swx_table_learner.c\n@@ -0,0 +1,617 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2020 Intel Corporation\n+ */\n+#include <stdlib.h>\n+#include <string.h>\n+#include <stdio.h>\n+#include <errno.h>\n+\n+#include <rte_common.h>\n+#include <rte_cycles.h>\n+#include <rte_prefetch.h>\n+\n+#include \"rte_swx_table_learner.h\"\n+\n+#ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES\n+#define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1\n+#endif\n+\n+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE\n+\n+#include <rte_malloc.h>\n+\n+static void *\n+env_calloc(size_t size, size_t alignment, int numa_node)\n+{\n+\treturn rte_zmalloc_socket(NULL, size, alignment, numa_node);\n+}\n+\n+static void\n+env_free(void *start, size_t size __rte_unused)\n+{\n+\trte_free(start);\n+}\n+\n+#else\n+\n+#include <numa.h>\n+\n+static void *\n+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)\n+{\n+\tvoid *start;\n+\n+\tif (numa_available() == -1)\n+\t\treturn NULL;\n+\n+\tstart = numa_alloc_onnode(size, numa_node);\n+\tif (!start)\n+\t\treturn NULL;\n+\n+\tmemset(start, 0, size);\n+\treturn start;\n+}\n+\n+static void\n+env_free(void *start, size_t size)\n+{\n+\tif ((numa_available() == -1) || !start)\n+\t\treturn;\n+\n+\tnuma_free(start, size);\n+}\n+\n+#endif\n+\n+#if defined(RTE_ARCH_X86_64)\n+\n+#include <x86intrin.h>\n+\n+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)\n+\n+#else\n+\n+static inline uint64_t\n+crc32_u64_generic(uint64_t crc, uint64_t value)\n+{\n+\tint i;\n+\n+\tcrc = (crc & 0xFFFFFFFFLLU) ^ value;\n+\tfor (i = 63; i >= 0; i--) {\n+\t\tuint64_t mask;\n+\n+\t\tmask = -(crc & 1LLU);\n+\t\tcrc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);\n+\t}\n+\n+\treturn crc;\n+}\n+\n+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)\n+\n+#endif\n+\n+/* Key size needs to be one of: 8, 16, 32 or 64. */\n+static inline uint32_t\n+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)\n+{\n+\tuint64_t *k = key;\n+\tuint64_t *m = key_mask;\n+\tuint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;\n+\n+\tswitch (key_size) {\n+\tcase 8:\n+\t\tcrc0 = crc32_u64(seed, k[0] & m[0]);\n+\t\treturn crc0;\n+\n+\tcase 16:\n+\t\tk0 = k[0] & m[0];\n+\n+\t\tcrc0 = crc32_u64(k0, seed);\n+\t\tcrc1 = crc32_u64(k0 >> 32, k[1] & m[1]);\n+\n+\t\tcrc0 ^= crc1;\n+\n+\t\treturn crc0;\n+\n+\tcase 32:\n+\t\tk0 = k[0] & m[0];\n+\t\tk2 = k[2] & m[2];\n+\n+\t\tcrc0 = crc32_u64(k0, seed);\n+\t\tcrc1 = crc32_u64(k0 >> 32, k[1] & m[1]);\n+\n+\t\tcrc2 = crc32_u64(k2, k[3] & m[3]);\n+\t\tcrc3 = k2 >> 32;\n+\n+\t\tcrc0 = crc32_u64(crc0, crc1);\n+\t\tcrc1 = crc32_u64(crc2, crc3);\n+\n+\t\tcrc0 ^= crc1;\n+\n+\t\treturn crc0;\n+\n+\tcase 64:\n+\t\tk0 = k[0] & m[0];\n+\t\tk2 = k[2] & m[2];\n+\t\tk5 = k[5] & m[5];\n+\n+\t\tcrc0 = crc32_u64(k0, seed);\n+\t\tcrc1 = crc32_u64(k0 >> 32, k[1] & m[1]);\n+\n+\t\tcrc2 = crc32_u64(k2, k[3] & m[3]);\n+\t\tcrc3 = crc32_u64(k2 >> 32, k[4] & m[4]);\n+\n+\t\tcrc4 = crc32_u64(k5, k[6] & m[6]);\n+\t\tcrc5 = crc32_u64(k5 >> 32, k[7] & m[7]);\n+\n+\t\tcrc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);\n+\t\tcrc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);\n+\n+\t\tcrc0 ^= crc1;\n+\n+\t\treturn crc0;\n+\n+\tdefault:\n+\t\tcrc0 = 0;\n+\t\treturn crc0;\n+\t}\n+}\n+\n+/*\n+ * Return: 0 = Keys are NOT equal; 1 = Keys are equal.\n+ */\n+static inline uint32_t\n+table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)\n+{\n+\tuint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask;\n+\n+\tswitch (n_bytes) {\n+\tcase 8: {\n+\t\tuint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);\n+\t\tuint32_t result = 1;\n+\n+\t\tif (xor0)\n+\t\t\tresult = 0;\n+\t\treturn result;\n+\t}\n+\n+\tcase 16: {\n+\t\tuint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);\n+\t\tuint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);\n+\t\tuint64_t or = xor0 | xor1;\n+\t\tuint32_t result = 1;\n+\n+\t\tif (or)\n+\t\t\tresult = 0;\n+\t\treturn result;\n+\t}\n+\n+\tcase 32: {\n+\t\tuint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);\n+\t\tuint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);\n+\t\tuint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);\n+\t\tuint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);\n+\t\tuint64_t or = (xor0 | xor1) | (xor2 | xor3);\n+\t\tuint32_t result = 1;\n+\n+\t\tif (or)\n+\t\t\tresult = 0;\n+\t\treturn result;\n+\t}\n+\n+\tcase 64: {\n+\t\tuint64_t xor0 = a64[0] ^ (b64[0] & b_mask64[0]);\n+\t\tuint64_t xor1 = a64[1] ^ (b64[1] & b_mask64[1]);\n+\t\tuint64_t xor2 = a64[2] ^ (b64[2] & b_mask64[2]);\n+\t\tuint64_t xor3 = a64[3] ^ (b64[3] & b_mask64[3]);\n+\t\tuint64_t xor4 = a64[4] ^ (b64[4] & b_mask64[4]);\n+\t\tuint64_t xor5 = a64[5] ^ (b64[5] & b_mask64[5]);\n+\t\tuint64_t xor6 = a64[6] ^ (b64[6] & b_mask64[6]);\n+\t\tuint64_t xor7 = a64[7] ^ (b64[7] & b_mask64[7]);\n+\t\tuint64_t or = ((xor0 | xor1) | (xor2 | xor3)) |\n+\t\t\t      ((xor4 | xor5) | (xor6 | xor7));\n+\t\tuint32_t result = 1;\n+\n+\t\tif (or)\n+\t\t\tresult = 0;\n+\t\treturn result;\n+\t}\n+\n+\tdefault: {\n+\t\tuint32_t i;\n+\n+\t\tfor (i = 0; i < n_bytes / sizeof(uint64_t); i++)\n+\t\t\tif (a64[i] != (b64[i] & b_mask64[i]))\n+\t\t\t\treturn 0;\n+\t\treturn 1;\n+\t}\n+\t}\n+}\n+\n+#define TABLE_KEYS_PER_BUCKET 4\n+\n+#define TABLE_BUCKET_PAD_SIZE \\\n+\t(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))\n+\n+struct table_bucket {\n+\tuint32_t time[TABLE_KEYS_PER_BUCKET];\n+\tuint32_t sig[TABLE_KEYS_PER_BUCKET];\n+\tuint8_t pad[TABLE_BUCKET_PAD_SIZE];\n+\tuint8_t key[0];\n+};\n+\n+struct table_params {\n+\t/* The real key size. Must be non-zero. */\n+\tsize_t key_size;\n+\n+\t/* They key size upgrated to the next power of 2. This used for hash generation (in\n+\t * increments of 8 bytes, from 8 to 64 bytes) and for run-time key comparison. This is why\n+\t * key sizes bigger than 64 bytes are not allowed.\n+\t */\n+\tsize_t key_size_pow2;\n+\n+\t/* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */\n+\tsize_t key_size_log2;\n+\n+\t/* The key offset within the key buffer. */\n+\tsize_t key_offset;\n+\n+\t/* The real action data size. */\n+\tsize_t action_data_size;\n+\n+\t/* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the\n+\t * next power of 2.\n+\t */\n+\tsize_t data_size_pow2;\n+\n+\t/* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */\n+\tsize_t data_size_log2;\n+\n+\t/* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */\n+\tsize_t n_buckets;\n+\n+\t/* Bucket mask. Purpose: replace modulo with bitmask and operation. */\n+\tsize_t bucket_mask;\n+\n+\t/* Total number of key bytes in the bucket, including the key padding bytes. There are\n+\t * (key_size_pow2 - key_size) padding bytes for each key in the bucket.\n+\t */\n+\tsize_t bucket_key_all_size;\n+\n+\t/* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */\n+\tsize_t bucket_size;\n+\n+\t/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */\n+\tsize_t bucket_size_log2;\n+\n+\t/* Timeout in CPU clock cycles. */\n+\tuint64_t key_timeout;\n+\n+\t/* Total memory size. */\n+\tsize_t total_size;\n+};\n+\n+struct table {\n+\t/* Table parameters. */\n+\tstruct table_params params;\n+\n+\t/* Key mask. Array of *key_size* bytes. */\n+\tuint8_t key_mask0[RTE_CACHE_LINE_SIZE];\n+\n+\t/* Table buckets. */\n+\tuint8_t buckets[0];\n+} __rte_cache_aligned;\n+\n+static int\n+table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)\n+{\n+\t/* Check input parameters. */\n+\tif (!params ||\n+\t    !params->key_size ||\n+\t    (params->key_size > 64) ||\n+\t    !params->n_keys_max ||\n+\t    (params->n_keys_max > 1U << 31) ||\n+\t    !params->key_timeout)\n+\t\treturn -EINVAL;\n+\n+\t/* Key. */\n+\tp->key_size = params->key_size;\n+\n+\tp->key_size_pow2 = rte_align64pow2(p->key_size);\n+\tif (p->key_size_pow2 < 8)\n+\t\tp->key_size_pow2 = 8;\n+\n+\tp->key_size_log2 = __builtin_ctzll(p->key_size_pow2);\n+\n+\tp->key_offset = params->key_offset;\n+\n+\t/* Data. */\n+\tp->action_data_size = params->action_data_size;\n+\n+\tp->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);\n+\n+\tp->data_size_log2 = __builtin_ctzll(p->data_size_pow2);\n+\n+\t/* Buckets. */\n+\tp->n_buckets = rte_align32pow2(params->n_keys_max);\n+\n+\tp->bucket_mask = p->n_buckets - 1;\n+\n+\tp->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;\n+\n+\tp->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +\n+\t\t\t\t\t p->bucket_key_all_size +\n+\t\t\t\t\t TABLE_KEYS_PER_BUCKET * p->data_size_pow2);\n+\n+\tp->bucket_size_log2 = __builtin_ctzll(p->bucket_size);\n+\n+\t/* Timeout. */\n+\tp->key_timeout = params->key_timeout * rte_get_tsc_hz();\n+\n+\t/* Total size. */\n+\tp->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;\n+\n+\treturn 0;\n+}\n+\n+static inline struct table_bucket *\n+table_bucket_get(struct table *t, size_t bucket_id)\n+{\n+\treturn (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];\n+}\n+\n+static inline uint8_t *\n+table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)\n+{\n+\treturn &b->key[bucket_key_pos << t->params.key_size_log2];\n+}\n+\n+static inline uint64_t *\n+table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)\n+{\n+\treturn (uint64_t *)&b->key[t->params.bucket_key_all_size +\n+\t\t\t\t   (bucket_key_pos << t->params.data_size_log2)];\n+}\n+\n+uint64_t\n+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)\n+{\n+\tstruct table_params p;\n+\tint status;\n+\n+\tstatus = table_params_get(&p, params);\n+\n+\treturn status ? 0 : p.total_size;\n+}\n+\n+void *\n+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)\n+{\n+\tstruct table_params p;\n+\tstruct table *t;\n+\tint status;\n+\n+\t/* Check and process the input parameters. */\n+\tstatus = table_params_get(&p, params);\n+\tif (status)\n+\t\treturn NULL;\n+\n+\t/* Memory allocation. */\n+\tt = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);\n+\tif (!t)\n+\t\treturn NULL;\n+\n+\t/* Memory initialization. */\n+\tmemcpy(&t->params, &p, sizeof(struct table_params));\n+\n+\tif (params->key_mask0)\n+\t\tmemcpy(t->key_mask0, params->key_mask0, params->key_size);\n+\telse\n+\t\tmemset(t->key_mask0, 0xFF, params->key_size);\n+\n+\treturn t;\n+}\n+\n+void\n+rte_swx_table_learner_free(void *table)\n+{\n+\tstruct table *t = table;\n+\n+\tif (!t)\n+\t\treturn;\n+\n+\tenv_free(t, t->params.total_size);\n+}\n+\n+struct mailbox {\n+\t/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */\n+\tstruct table_bucket *bucket;\n+\n+\t/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */\n+\tuint32_t input_sig;\n+\n+\t/* Writer: lookup state 1. Reader(s): add(). */\n+\tuint8_t *input_key;\n+\n+\t/* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */\n+\tuint32_t hit;\n+\n+\t/* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */\n+\tsize_t bucket_key_pos;\n+\n+\t/* State. */\n+\tint state;\n+};\n+\n+uint64_t\n+rte_swx_table_learner_mailbox_size_get(void)\n+{\n+\treturn sizeof(struct mailbox);\n+}\n+\n+int\n+rte_swx_table_learner_lookup(void *table,\n+\t\t\t     void *mailbox,\n+\t\t\t     uint64_t input_time,\n+\t\t\t     uint8_t **key,\n+\t\t\t     uint64_t *action_id,\n+\t\t\t     uint8_t **action_data,\n+\t\t\t     int *hit)\n+{\n+\tstruct table *t = table;\n+\tstruct mailbox *m = mailbox;\n+\n+\tswitch (m->state) {\n+\tcase 0: {\n+\t\tuint8_t *input_key;\n+\t\tstruct table_bucket *b;\n+\t\tsize_t bucket_id;\n+\t\tuint32_t input_sig;\n+\n+\t\tinput_key = &(*key)[t->params.key_offset];\n+\t\tinput_sig = hash(input_key, t->key_mask0, t->params.key_size_pow2, 0);\n+\t\tbucket_id = input_sig & t->params.bucket_mask;\n+\t\tb = table_bucket_get(t, bucket_id);\n+\n+\t\trte_prefetch0(b);\n+\t\trte_prefetch0(&b->key[0]);\n+\t\trte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);\n+\n+\t\tm->bucket = b;\n+\t\tm->input_key = input_key;\n+\t\tm->input_sig = input_sig | 1;\n+\t\tm->state = 1;\n+\t\treturn 0;\n+\t}\n+\n+\tcase 1: {\n+\t\tstruct table_bucket *b = m->bucket;\n+\t\tuint32_t i;\n+\n+\t\t/* Search the input key through the bucket keys. */\n+\t\tfor (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {\n+\t\t\tuint64_t time = b->time[i];\n+\t\t\tuint32_t sig = b->sig[i];\n+\t\t\tuint8_t *key = table_bucket_key_get(t, b, i);\n+\t\t\tuint32_t key_size_pow2 = t->params.key_size_pow2;\n+\n+\t\t\ttime <<= 32;\n+\n+\t\t\tif ((time > input_time) &&\n+\t\t\t    (sig == m->input_sig) &&\n+\t\t\t    table_keycmp(key, m->input_key, t->key_mask0, key_size_pow2)) {\n+\t\t\t\tuint64_t *data = table_bucket_data_get(t, b, i);\n+\n+\t\t\t\t/* Hit. */\n+\t\t\t\trte_prefetch0(data);\n+\n+\t\t\t\tb->time[i] = (input_time + t->params.key_timeout) >> 32;\n+\n+\t\t\t\tm->hit = 1;\n+\t\t\t\tm->bucket_key_pos = i;\n+\t\t\t\tm->state = 0;\n+\n+\t\t\t\t*action_id = data[0];\n+\t\t\t\t*action_data = (uint8_t *)&data[1];\n+\t\t\t\t*hit = 1;\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Miss. */\n+\t\tm->hit = 0;\n+\t\tm->state = 0;\n+\n+\t\t*hit = 0;\n+\t\treturn 1;\n+\t}\n+\n+\tdefault:\n+\t\t/* This state should never be reached. Miss. */\n+\t\tm->hit = 0;\n+\t\tm->state = 0;\n+\n+\t\t*hit = 0;\n+\t\treturn 1;\n+\t}\n+}\n+\n+uint32_t\n+rte_swx_table_learner_add(void *table,\n+\t\t\t  void *mailbox,\n+\t\t\t  uint64_t input_time,\n+\t\t\t  uint64_t action_id,\n+\t\t\t  uint8_t *action_data)\n+{\n+\tstruct table *t = table;\n+\tstruct mailbox *m = mailbox;\n+\tstruct table_bucket *b = m->bucket;\n+\tuint32_t i;\n+\n+\t/* Lookup hit: The key, key signature and key time are already properly configured (the key\n+\t * time was bumped by lookup), only the key data need to be updated.\n+\t */\n+\tif (m->hit) {\n+\t\tuint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);\n+\n+\t\t/* Install the key data. */\n+\t\tdata[0] = action_id;\n+\t\tif (t->params.action_data_size && action_data)\n+\t\t\tmemcpy(&data[1], action_data, t->params.action_data_size);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Lookup miss: Search for a free position in the current bucket and install the key. */\n+\tfor (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {\n+\t\tuint64_t time = b->time[i];\n+\n+\t\ttime <<= 32;\n+\n+\t\t/* Free position: Either there was never a key installed here, so the key time is\n+\t\t * set to zero (the init value), which is always less than the current time, or this\n+\t\t * position was used before, but the key expired (the key time is in the past).\n+\t\t */\n+\t\tif (time < input_time) {\n+\t\t\tuint8_t *key = table_bucket_key_get(t, b, i);\n+\t\t\tuint64_t *data = table_bucket_data_get(t, b, i);\n+\n+\t\t\t/* Install the key. */\n+\t\t\tb->time[i] = (input_time + t->params.key_timeout) >> 32;\n+\t\t\tb->sig[i] = m->input_sig;\n+\t\t\tmemcpy(key, m->input_key, t->params.key_size);\n+\n+\t\t\t/* Install the key data. */\n+\t\t\tdata[0] = action_id;\n+\t\t\tif (t->params.action_data_size && action_data)\n+\t\t\t\tmemcpy(&data[1], action_data, t->params.action_data_size);\n+\n+\t\t\t/* Mailbox. */\n+\t\t\tm->hit = 1;\n+\t\t\tm->bucket_key_pos = i;\n+\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t/* Bucket full. */\n+\treturn 1;\n+}\n+\n+void\n+rte_swx_table_learner_delete(void *table __rte_unused,\n+\t\t\t     void *mailbox)\n+{\n+\tstruct mailbox *m = mailbox;\n+\n+\tif (m->hit) {\n+\t\tstruct table_bucket *b = m->bucket;\n+\n+\t\t/* Expire the key. */\n+\t\tb->time[m->bucket_key_pos] = 0;\n+\n+\t\t/* Mailbox. */\n+\t\tm->hit = 0;\n+\t}\n+}\ndiff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h\nnew file mode 100644\nindex 0000000000..d6ec733655\n--- /dev/null\n+++ b/lib/table/rte_swx_table_learner.h\n@@ -0,0 +1,206 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2021 Intel Corporation\n+ */\n+#ifndef __INCLUDE_RTE_SWX_TABLE_LEARNER_H__\n+#define __INCLUDE_RTE_SWX_TABLE_LEARNER_H__\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+/**\n+ * @file\n+ * RTE SWX Learner Table\n+ *\n+ * The learner table API.\n+ *\n+ * This table type is typically used for learning or connection tracking, where it allows for the\n+ * implementation of the \"add on miss\" scenario: whenever the lookup key is not found in the table\n+ * (lookup miss), the data plane can decide to add this key to the table with a given action with no\n+ * control plane intervention. Likewise, the table keys expire based on a configurable timeout and\n+ * are automatically deleted from the table with no control plane intervention.\n+ */\n+\n+#include <stdint.h>\n+#include <sys/queue.h>\n+\n+#include <rte_compat.h>\n+\n+/** Learner table creation parameters. */\n+struct rte_swx_table_learner_params {\n+\t/** Key size in bytes. Must be non-zero. */\n+\tuint32_t key_size;\n+\n+\t/** Offset of the first byte of the key within the key buffer. */\n+\tuint32_t key_offset;\n+\n+\t/** Mask of *key_size* bytes logically laid over the bytes at positions\n+\t * *key_offset* .. (*key_offset* + *key_size* - 1) of the key buffer in order to specify\n+\t * which bits from the key buffer are part of the key and which ones are not. A bit value of\n+\t * 1 in the *key_mask0* means the respective bit in the key buffer is part of the key, while\n+\t * a bit value of 0 means the opposite. A NULL value means that all the bits are part of the\n+\t * key, i.e. the *key_mask0* is an all-ones mask.\n+\t */\n+\tuint8_t *key_mask0;\n+\n+\t/** Maximum size (in bytes) of the action data. The data stored in the table for each entry\n+\t * is equal to *action_data_size* plus 8 bytes, which are used to store the action ID.\n+\t */\n+\tuint32_t action_data_size;\n+\n+\t/** Maximum number of keys to be stored in the table together with their associated data. */\n+\tuint32_t n_keys_max;\n+\n+\t/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically\n+\t * deleted from the table after this many seconds.\n+\t */\n+\tuint32_t key_timeout;\n+};\n+\n+/**\n+ * Learner table memory footprint get\n+ *\n+ * @param[in] params\n+ *   Table create parameters.\n+ * @return\n+ *   Table memory footprint in bytes.\n+ */\n+__rte_experimental\n+uint64_t\n+rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params);\n+\n+/**\n+ * Learner table mailbox size get\n+ *\n+ * The mailbox is used to store the context of a lookup operation that is in\n+ * progress and it is passed as a parameter to the lookup operation. This allows\n+ * for multiple concurrent lookup operations into the same table.\n+ *\n+ * @return\n+ *   Table mailbox footprint in bytes.\n+ */\n+__rte_experimental\n+uint64_t\n+rte_swx_table_learner_mailbox_size_get(void);\n+\n+/**\n+ * Learner table create\n+ *\n+ * @param[in] params\n+ *   Table creation parameters.\n+ * @param[in] numa_node\n+ *   Non-Uniform Memory Access (NUMA) node.\n+ * @return\n+ *   Table handle, on success, or NULL, on error.\n+ */\n+__rte_experimental\n+void *\n+rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);\n+\n+/**\n+ * Learner table key lookup\n+ *\n+ * The table lookup operation searches a given key in the table and upon its completion it returns\n+ * an indication of whether the key is found in the table (lookup hit) or not (lookup miss). In case\n+ * of lookup hit, the action_id and the action_data associated with the key are also returned.\n+ *\n+ * Multiple invocations of this function may be required in order to complete a single table lookup\n+ * operation for a given table and a given lookup key. The completion of the table lookup operation\n+ * is flagged by a return value of 1; in case of a return value of 0, the function must be invoked\n+ * again with exactly the same arguments.\n+ *\n+ * The mailbox argument is used to store the context of an on-going table key lookup operation, and\n+ * possibly an associated key add operation. The mailbox mechanism allows for multiple concurrent\n+ * table key lookup and add operations into the same table.\n+ *\n+ * @param[in] table\n+ *   Table handle.\n+ * @param[in] mailbox\n+ *   Mailbox for the current table lookup operation.\n+ * @param[in] time\n+ *   Current time measured in CPU clock cycles.\n+ * @param[in] key\n+ *   Lookup key. Its size must be equal to the table *key_size*.\n+ * @param[out] action_id\n+ *   ID of the action associated with the *key*. Must point to a valid 64-bit variable. Only valid\n+ *   when the function returns 1 and *hit* is set to true.\n+ * @param[out] action_data\n+ *   Action data for the *action_id* action. Must point to a valid array of table *action_data_size*\n+ *   bytes. Only valid when the function returns 1 and *hit* is set to true.\n+ * @param[out] hit\n+ *   Only valid when the function returns 1. Set to non-zero (true) on table lookup hit and to zero\n+ *   (false) on table lookup miss.\n+ * @return\n+ *   0 when the table lookup operation is not yet completed, and 1 when the table lookup operation\n+ *   is completed. No other return values are allowed.\n+ */\n+__rte_experimental\n+int\n+rte_swx_table_learner_lookup(void *table,\n+\t\t\t     void *mailbox,\n+\t\t\t     uint64_t time,\n+\t\t\t     uint8_t **key,\n+\t\t\t     uint64_t *action_id,\n+\t\t\t     uint8_t **action_data,\n+\t\t\t     int *hit);\n+\n+/**\n+ * Learner table key add\n+ *\n+ * This operation takes the latest key that was looked up in the table and adds it to the table with\n+ * the given action ID and action data. Typically, this operation is only invoked when the latest\n+ * lookup operation in the current table resulted in lookup miss.\n+ *\n+ * @param[in] table\n+ *   Table handle.\n+ * @param[in] mailbox\n+ *   Mailbox for the current operation.\n+ * @param[in] time\n+ *   Current time measured in CPU clock cycles.\n+ * @param[out] action_id\n+ *   ID of the action associated with the key.\n+ * @param[out] action_data\n+ *   Action data for the *action_id* action.\n+ * @return\n+ *   0 on success, 1 or error (table full).\n+ */\n+__rte_experimental\n+uint32_t\n+rte_swx_table_learner_add(void *table,\n+\t\t\t  void *mailbox,\n+\t\t\t  uint64_t time,\n+\t\t\t  uint64_t action_id,\n+\t\t\t  uint8_t *action_data);\n+\n+/**\n+ * Learner table key delete\n+ *\n+ * This operation takes the latest key that was looked up in the table and deletes it from the\n+ * table. Typically, this operation is only invoked to force the deletion of the key before the key\n+ * expires on timeout due to inactivity.\n+ *\n+ * @param[in] table\n+ *   Table handle.\n+ * @param[in] mailbox\n+ *   Mailbox for the current operation.\n+ */\n+__rte_experimental\n+void\n+rte_swx_table_learner_delete(void *table,\n+\t\t\t     void *mailbox);\n+\n+/**\n+ * Learner table free\n+ *\n+ * @param[in] table\n+ *   Table handle.\n+ */\n+__rte_experimental\n+void\n+rte_swx_table_learner_free(void *table);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\ndiff --git a/lib/table/version.map b/lib/table/version.map\nindex 65f9645d25..efe5f6e52c 100644\n--- a/lib/table/version.map\n+++ b/lib/table/version.map\n@@ -36,4 +36,13 @@ EXPERIMENTAL {\n \trte_swx_table_selector_group_set;\n \trte_swx_table_selector_mailbox_size_get;\n \trte_swx_table_selector_select;\n+\n+\t# added in 21.11\n+\trte_swx_table_learner_add;\n+\trte_swx_table_learner_create;\n+\trte_swx_table_learner_delete;\n+\trte_swx_table_learner_footprint_get;\n+\trte_swx_table_learner_free;\n+\trte_swx_table_learner_lookup;\n+\trte_swx_table_learner_mailbox_size_get;\n };\n",
    "prefixes": [
        "V5",
        "1/4"
    ]
}