get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 96929,
    "url": "https://patches.dpdk.org/api/patches/96929/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20210814135949.75631-2-cristian.dumitrescu@intel.com/",
    "project": {
        "id": 1,
        "url": "https://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": "<20210814135949.75631-2-cristian.dumitrescu@intel.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20210814135949.75631-2-cristian.dumitrescu@intel.com",
    "date": "2021-08-14T13:59:47",
    "name": "[V3,2/4] pipeline: add support for learner tables",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "f094d394126484251294160555cfa88b63cfecec",
    "submitter": {
        "id": 19,
        "url": "https://patches.dpdk.org/api/people/19/?format=api",
        "name": "Cristian Dumitrescu",
        "email": "cristian.dumitrescu@intel.com"
    },
    "delegate": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "https://patches.dpdk.org/project/dpdk/patch/20210814135949.75631-2-cristian.dumitrescu@intel.com/mbox/",
    "series": [
        {
            "id": 18288,
            "url": "https://patches.dpdk.org/api/series/18288/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=18288",
            "date": "2021-08-14T13:59:46",
            "name": "[V3,1/4] table: add support learner tables",
            "version": 3,
            "mbox": "https://patches.dpdk.org/series/18288/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/96929/comments/",
    "check": "success",
    "checks": "https://patches.dpdk.org/api/patches/96929/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 AE3CDA0C57;\n\tSat, 14 Aug 2021 16:00:09 +0200 (CEST)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 4B0844119F;\n\tSat, 14 Aug 2021 15:59:57 +0200 (CEST)",
            "from mga18.intel.com (mga18.intel.com [134.134.136.126])\n by mails.dpdk.org (Postfix) with ESMTP id 7A5624068F\n for <dev@dpdk.org>; Sat, 14 Aug 2021 15:59:53 +0200 (CEST)",
            "from fmsmga008.fm.intel.com ([10.253.24.58])\n by orsmga106.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 14 Aug 2021 06:59:52 -0700",
            "from silpixa00400573.ir.intel.com (HELO\n silpixa00400573.ger.corp.intel.com) ([10.237.223.107])\n by fmsmga008.fm.intel.com with ESMTP; 14 Aug 2021 06:59:51 -0700"
        ],
        "X-IronPort-AV": [
            "E=McAfee;i=\"6200,9189,10075\"; a=\"202842112\"",
            "E=Sophos;i=\"5.84,321,1620716400\"; d=\"scan'208\";a=\"202842112\"",
            "E=Sophos;i=\"5.84,321,1620716400\"; d=\"scan'208\";a=\"486417979\""
        ],
        "X-ExtLoop1": "1",
        "From": "Cristian Dumitrescu <cristian.dumitrescu@intel.com>",
        "To": "dev@dpdk.org",
        "Date": "Sat, 14 Aug 2021 14:59:47 +0100",
        "Message-Id": "<20210814135949.75631-2-cristian.dumitrescu@intel.com>",
        "X-Mailer": "git-send-email 2.17.1",
        "In-Reply-To": "<20210814135949.75631-1-cristian.dumitrescu@intel.com>",
        "References": "<20210814134326.73736-1-cristian.dumitrescu@intel.com>\n <20210814135949.75631-1-cristian.dumitrescu@intel.com>",
        "Subject": "[dpdk-dev] [PATCH V3 2/4] pipeline: add support for 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": "Add pipeline level support for learner tables.\n\nSigned-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>\n---\n\nV2: Added more configuration consistency checks.\nV3: Fixed one coding style indentation error.\n\n lib/pipeline/rte_swx_ctl.c           |  479 +++++++++++-\n lib/pipeline/rte_swx_ctl.h           |  185 +++++\n lib/pipeline/rte_swx_pipeline.c      | 1041 ++++++++++++++++++++++++--\n lib/pipeline/rte_swx_pipeline.h      |   77 ++\n lib/pipeline/rte_swx_pipeline_spec.c |  470 +++++++++++-\n lib/pipeline/version.map             |    8 +\n 6 files changed, 2205 insertions(+), 55 deletions(-)",
    "diff": "diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c\nindex dc093860de..86b58e21dc 100644\n--- a/lib/pipeline/rte_swx_ctl.c\n+++ b/lib/pipeline/rte_swx_ctl.c\n@@ -123,12 +123,26 @@ struct selector {\n \tstruct rte_swx_table_selector_params params;\n };\n \n+struct learner {\n+\tstruct rte_swx_ctl_learner_info info;\n+\tstruct rte_swx_ctl_table_match_field_info *mf;\n+\tstruct rte_swx_ctl_table_action_info *actions;\n+\tuint32_t action_data_size;\n+\n+\t/* The pending default action: this is NOT the current default action;\n+\t * this will be the new default action after the next commit, if the\n+\t * next commit operation is successful.\n+\t */\n+\tstruct rte_swx_table_entry *pending_default;\n+};\n+\n struct rte_swx_ctl_pipeline {\n \tstruct rte_swx_ctl_pipeline_info info;\n \tstruct rte_swx_pipeline *p;\n \tstruct action *actions;\n \tstruct table *tables;\n \tstruct selector *selectors;\n+\tstruct learner *learners;\n \tstruct rte_swx_table_state *ts;\n \tstruct rte_swx_table_state *ts_next;\n \tint numa_node;\n@@ -924,6 +938,70 @@ selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n \treturn 0;\n }\n \n+static void\n+learner_pending_default_free(struct learner *l)\n+{\n+\tif (!l->pending_default)\n+\t\treturn;\n+\n+\tfree(l->pending_default->action_data);\n+\tfree(l->pending_default);\n+\tl->pending_default = NULL;\n+}\n+\n+\n+static void\n+learner_free(struct rte_swx_ctl_pipeline *ctl)\n+{\n+\tuint32_t i;\n+\n+\tif (!ctl->learners)\n+\t\treturn;\n+\n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tstruct learner *l = &ctl->learners[i];\n+\n+\t\tfree(l->mf);\n+\t\tfree(l->actions);\n+\n+\t\tlearner_pending_default_free(l);\n+\t}\n+\n+\tfree(ctl->learners);\n+\tctl->learners = NULL;\n+}\n+\n+static struct learner *\n+learner_find(struct rte_swx_ctl_pipeline *ctl, const char *learner_name)\n+{\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tstruct learner *l = &ctl->learners[i];\n+\n+\t\tif (!strcmp(learner_name, l->info.name))\n+\t\t\treturn l;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static uint32_t\n+learner_action_data_size_get(struct rte_swx_ctl_pipeline *ctl, struct learner *l)\n+{\n+\tuint32_t action_data_size = 0, i;\n+\n+\tfor (i = 0; i < l->info.n_actions; i++) {\n+\t\tuint32_t action_id = l->actions[i].action_id;\n+\t\tstruct action *a = &ctl->actions[action_id];\n+\n+\t\tif (a->data_size > action_data_size)\n+\t\t\taction_data_size = a->data_size;\n+\t}\n+\n+\treturn action_data_size;\n+}\n+\n static void\n table_state_free(struct rte_swx_ctl_pipeline *ctl)\n {\n@@ -954,6 +1032,14 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)\n \t\t\trte_swx_table_selector_free(ts->obj);\n \t}\n \n+\t/* For each learner table, free its table state. */\n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tstruct rte_swx_table_state *ts = &ctl->ts_next[i];\n+\n+\t\t/* Default action data. */\n+\t\tfree(ts->default_action_data);\n+\t}\n+\n \tfree(ctl->ts_next);\n \tctl->ts_next = NULL;\n }\n@@ -1020,6 +1106,29 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)\n \t\t}\n \t}\n \n+\t/* Learner tables. */\n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tstruct learner *l = &ctl->learners[i];\n+\t\tstruct rte_swx_table_state *ts = &ctl->ts[i];\n+\t\tstruct rte_swx_table_state *ts_next = &ctl->ts_next[i];\n+\n+\t\t/* Table object: duplicate from the current table state. */\n+\t\tts_next->obj = ts->obj;\n+\n+\t\t/* Default action data: duplicate from the current table state. */\n+\t\tts_next->default_action_data = malloc(l->action_data_size);\n+\t\tif (!ts_next->default_action_data) {\n+\t\t\tstatus = -ENOMEM;\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tmemcpy(ts_next->default_action_data,\n+\t\t       ts->default_action_data,\n+\t\t       l->action_data_size);\n+\n+\t\tts_next->default_action_id = ts->default_action_id;\n+\t}\n+\n \treturn 0;\n \n error:\n@@ -1037,6 +1146,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)\n \n \ttable_state_free(ctl);\n \n+\tlearner_free(ctl);\n+\n \tselector_free(ctl);\n \n \ttable_free(ctl);\n@@ -1251,6 +1362,54 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)\n \t\t\tgoto error;\n \t}\n \n+\t/* learner tables. */\n+\tctl->learners = calloc(ctl->info.n_learners, sizeof(struct learner));\n+\tif (!ctl->learners)\n+\t\tgoto error;\n+\n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tstruct learner *l = &ctl->learners[i];\n+\t\tuint32_t j;\n+\n+\t\t/* info. */\n+\t\tstatus = rte_swx_ctl_learner_info_get(p, i, &l->info);\n+\t\tif (status)\n+\t\t\tgoto error;\n+\n+\t\t/* mf. */\n+\t\tl->mf = calloc(l->info.n_match_fields,\n+\t\t\t       sizeof(struct rte_swx_ctl_table_match_field_info));\n+\t\tif (!l->mf)\n+\t\t\tgoto error;\n+\n+\t\tfor (j = 0; j < l->info.n_match_fields; j++) {\n+\t\t\tstatus = rte_swx_ctl_learner_match_field_info_get(p,\n+\t\t\t\ti,\n+\t\t\t\tj,\n+\t\t\t\t&l->mf[j]);\n+\t\t\tif (status)\n+\t\t\t\tgoto error;\n+\t\t}\n+\n+\t\t/* actions. */\n+\t\tl->actions = calloc(l->info.n_actions,\n+\t\t\tsizeof(struct rte_swx_ctl_table_action_info));\n+\t\tif (!l->actions)\n+\t\t\tgoto error;\n+\n+\t\tfor (j = 0; j < l->info.n_actions; j++) {\n+\t\t\tstatus = rte_swx_ctl_learner_action_info_get(p,\n+\t\t\t\ti,\n+\t\t\t\tj,\n+\t\t\t\t&l->actions[j]);\n+\t\t\tif (status || l->actions[j].action_id >= ctl->info.n_actions)\n+\t\t\t\tgoto error;\n+\t\t}\n+\n+\t\t/* action_data_size. */\n+\t\tl->action_data_size = learner_action_data_size_get(ctl, l);\n+\t}\n+\n \t/* ts. */\n \tstatus = rte_swx_pipeline_table_state_get(p, &ctl->ts);\n \tif (status)\n@@ -1685,9 +1844,8 @@ table_rollfwd1(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)\n \taction_data = table->pending_default->action_data;\n \ta = &ctl->actions[action_id];\n \n-\tmemcpy(ts_next->default_action_data,\n-\t       action_data,\n-\t       a->data_size);\n+\tif (a->data_size)\n+\t\tmemcpy(ts_next->default_action_data, action_data, a->data_size);\n \n \tts_next->default_action_id = action_id;\n }\n@@ -2099,6 +2257,178 @@ selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n \tmemset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));\n }\n \n+static struct rte_swx_table_entry *\n+learner_default_entry_alloc(struct learner *l)\n+{\n+\tstruct rte_swx_table_entry *entry;\n+\n+\tentry = calloc(1, sizeof(struct rte_swx_table_entry));\n+\tif (!entry)\n+\t\tgoto error;\n+\n+\t/* action_data. */\n+\tif (l->action_data_size) {\n+\t\tentry->action_data = calloc(1, l->action_data_size);\n+\t\tif (!entry->action_data)\n+\t\t\tgoto error;\n+\t}\n+\n+\treturn entry;\n+\n+error:\n+\ttable_entry_free(entry);\n+\treturn NULL;\n+}\n+\n+static int\n+learner_default_entry_check(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t    uint32_t learner_id,\n+\t\t\t    struct rte_swx_table_entry *entry)\n+{\n+\tstruct learner *l = &ctl->learners[learner_id];\n+\tstruct action *a;\n+\tuint32_t i;\n+\n+\tCHECK(entry, EINVAL);\n+\n+\t/* action_id. */\n+\tfor (i = 0; i < l->info.n_actions; i++)\n+\t\tif (entry->action_id == l->actions[i].action_id)\n+\t\t\tbreak;\n+\n+\tCHECK(i < l->info.n_actions, EINVAL);\n+\n+\t/* action_data. */\n+\ta = &ctl->actions[entry->action_id];\n+\tCHECK(!(a->data_size && !entry->action_data), EINVAL);\n+\n+\treturn 0;\n+}\n+\n+static struct rte_swx_table_entry *\n+learner_default_entry_duplicate(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\tuint32_t learner_id,\n+\t\t\t\tstruct rte_swx_table_entry *entry)\n+{\n+\tstruct learner *l = &ctl->learners[learner_id];\n+\tstruct rte_swx_table_entry *new_entry = NULL;\n+\tstruct action *a;\n+\tuint32_t i;\n+\n+\tif (!entry)\n+\t\tgoto error;\n+\n+\tnew_entry = calloc(1, sizeof(struct rte_swx_table_entry));\n+\tif (!new_entry)\n+\t\tgoto error;\n+\n+\t/* action_id. */\n+\tfor (i = 0; i < l->info.n_actions; i++)\n+\t\tif (entry->action_id == l->actions[i].action_id)\n+\t\t\tbreak;\n+\n+\tif (i >= l->info.n_actions)\n+\t\tgoto error;\n+\n+\tnew_entry->action_id = entry->action_id;\n+\n+\t/* action_data. */\n+\ta = &ctl->actions[entry->action_id];\n+\tif (a->data_size && !entry->action_data)\n+\t\tgoto error;\n+\n+\t/* The table layer provisions a constant action data size per\n+\t * entry, which should be the largest data size for all the\n+\t * actions enabled for the current table, and attempts to copy\n+\t * this many bytes each time a table entry is added, even if the\n+\t * specific action requires less data or even no data at all,\n+\t * hence we always have to allocate the max.\n+\t */\n+\tnew_entry->action_data = calloc(1, l->action_data_size);\n+\tif (!new_entry->action_data)\n+\t\tgoto error;\n+\n+\tif (a->data_size)\n+\t\tmemcpy(new_entry->action_data, entry->action_data, a->data_size);\n+\n+\treturn new_entry;\n+\n+error:\n+\ttable_entry_free(new_entry);\n+\treturn NULL;\n+}\n+\n+int\n+rte_swx_ctl_pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t       const char *learner_name,\n+\t\t\t\t\t       struct rte_swx_table_entry *entry)\n+{\n+\tstruct learner *l;\n+\tstruct rte_swx_table_entry *new_entry;\n+\tuint32_t learner_id;\n+\n+\tCHECK(ctl, EINVAL);\n+\n+\tCHECK(learner_name && learner_name[0], EINVAL);\n+\tl = learner_find(ctl, learner_name);\n+\tCHECK(l, EINVAL);\n+\tlearner_id = l - ctl->learners;\n+\tCHECK(!l->info.default_action_is_const, EINVAL);\n+\n+\tCHECK(entry, EINVAL);\n+\tCHECK(!learner_default_entry_check(ctl, learner_id, entry), EINVAL);\n+\n+\tnew_entry = learner_default_entry_duplicate(ctl, learner_id, entry);\n+\tCHECK(new_entry, ENOMEM);\n+\n+\tlearner_pending_default_free(l);\n+\n+\tl->pending_default = new_entry;\n+\treturn 0;\n+}\n+\n+static void\n+learner_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)\n+{\n+\tstruct learner *l = &ctl->learners[learner_id];\n+\tstruct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables +\n+\t\tctl->info.n_selectors + learner_id];\n+\tstruct action *a;\n+\tuint8_t *action_data;\n+\tuint64_t action_id;\n+\n+\t/* Copy the pending default entry. */\n+\tif (!l->pending_default)\n+\t\treturn;\n+\n+\taction_id = l->pending_default->action_id;\n+\taction_data = l->pending_default->action_data;\n+\ta = &ctl->actions[action_id];\n+\n+\tif (a->data_size)\n+\t\tmemcpy(ts_next->default_action_data, action_data, a->data_size);\n+\n+\tts_next->default_action_id = action_id;\n+}\n+\n+static void\n+learner_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)\n+{\n+\tstruct learner *l = &ctl->learners[learner_id];\n+\n+\t/* Free up the pending default entry, as it is now part of the table. */\n+\tlearner_pending_default_free(l);\n+}\n+\n+static void\n+learner_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)\n+{\n+\tstruct learner *l = &ctl->learners[learner_id];\n+\n+\t/* Free up the pending default entry, as it is no longer going to be added to the table. */\n+\tlearner_pending_default_free(l);\n+}\n+\n int\n rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n {\n@@ -2110,6 +2440,7 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \n \t/* Operate the changes on the current ts_next before it becomes the new ts. First, operate\n \t * all the changes that can fail; if no failure, then operate the changes that cannot fail.\n+\t * We must be able to fully revert all the changes that can fail as if they never happened.\n \t */\n \tfor (i = 0; i < ctl->info.n_tables; i++) {\n \t\tstatus = table_rollfwd0(ctl, i, 0);\n@@ -2123,9 +2454,15 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\t\tgoto rollback;\n \t}\n \n+\t/* Second, operate all the changes that cannot fail. Since nothing can fail from this point\n+\t * onwards, the transaction is guaranteed to be successful.\n+\t */\n \tfor (i = 0; i < ctl->info.n_tables; i++)\n \t\ttable_rollfwd1(ctl, i);\n \n+\tfor (i = 0; i < ctl->info.n_learners; i++)\n+\t\tlearner_rollfwd(ctl, i);\n+\n \t/* Swap the table state for the data plane. The current ts and ts_next\n \t * become the new ts_next and ts, respectively.\n \t */\n@@ -2151,6 +2488,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\tselector_rollfwd_finalize(ctl, i);\n \t}\n \n+\tfor (i = 0; i < ctl->info.n_learners; i++) {\n+\t\tlearner_rollfwd(ctl, i);\n+\t\tlearner_rollfwd_finalize(ctl, i);\n+\t}\n+\n \treturn 0;\n \n rollback:\n@@ -2166,6 +2508,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\t\tselector_abort(ctl, i);\n \t}\n \n+\tif (abort_on_fail)\n+\t\tfor (i = 0; i < ctl->info.n_learners; i++)\n+\t\t\tlearner_abort(ctl, i);\n+\n \treturn status;\n }\n \n@@ -2182,6 +2528,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)\n \n \tfor (i = 0; i < ctl->info.n_selectors; i++)\n \t\tselector_abort(ctl, i);\n+\n+\tfor (i = 0; i < ctl->info.n_learners; i++)\n+\t\tlearner_abort(ctl, i);\n }\n \n static int\n@@ -2460,6 +2809,130 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,\n \treturn NULL;\n }\n \n+struct rte_swx_table_entry *\n+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t\tconst char *learner_name,\n+\t\t\t\t\t\tconst char *string,\n+\t\t\t\t\t\tint *is_blank_or_comment)\n+{\n+\tchar *token_array[RTE_SWX_CTL_ENTRY_TOKENS_MAX], **tokens;\n+\tstruct learner *l;\n+\tstruct action *action;\n+\tstruct rte_swx_table_entry *entry = NULL;\n+\tchar *s0 = NULL, *s;\n+\tuint32_t n_tokens = 0, arg_offset = 0, i;\n+\tint blank_or_comment = 0;\n+\n+\t/* Check input arguments. */\n+\tif (!ctl)\n+\t\tgoto error;\n+\n+\tif (!learner_name || !learner_name[0])\n+\t\tgoto error;\n+\n+\tl = learner_find(ctl, learner_name);\n+\tif (!l)\n+\t\tgoto error;\n+\n+\tif (!string || !string[0])\n+\t\tgoto error;\n+\n+\t/* Memory allocation. */\n+\ts0 = strdup(string);\n+\tif (!s0)\n+\t\tgoto error;\n+\n+\tentry = learner_default_entry_alloc(l);\n+\tif (!entry)\n+\t\tgoto error;\n+\n+\t/* Parse the string into tokens. */\n+\tfor (s = s0; ; ) {\n+\t\tchar *token;\n+\n+\t\ttoken = strtok_r(s, \" \\f\\n\\r\\t\\v\", &s);\n+\t\tif (!token || token_is_comment(token))\n+\t\t\tbreak;\n+\n+\t\tif (n_tokens >= RTE_SWX_CTL_ENTRY_TOKENS_MAX)\n+\t\t\tgoto error;\n+\n+\t\ttoken_array[n_tokens] = token;\n+\t\tn_tokens++;\n+\t}\n+\n+\tif (!n_tokens) {\n+\t\tblank_or_comment = 1;\n+\t\tgoto error;\n+\t}\n+\n+\ttokens = token_array;\n+\n+\t/*\n+\t * Action.\n+\t */\n+\tif (!(n_tokens && !strcmp(tokens[0], \"action\")))\n+\t\tgoto other;\n+\n+\tif (n_tokens < 2)\n+\t\tgoto error;\n+\n+\taction = action_find(ctl, tokens[1]);\n+\tif (!action)\n+\t\tgoto error;\n+\n+\tif (n_tokens < 2 + action->info.n_args * 2)\n+\t\tgoto error;\n+\n+\t/* action_id. */\n+\tentry->action_id = action - ctl->actions;\n+\n+\t/* action_data. */\n+\tfor (i = 0; i < action->info.n_args; i++) {\n+\t\tstruct rte_swx_ctl_action_arg_info *arg = &action->args[i];\n+\t\tchar *arg_name, *arg_val;\n+\t\tuint64_t val;\n+\n+\t\targ_name = tokens[2 + i * 2];\n+\t\targ_val = tokens[2 + i * 2 + 1];\n+\n+\t\tif (strcmp(arg_name, arg->name))\n+\t\t\tgoto error;\n+\n+\t\tval = strtoull(arg_val, &arg_val, 0);\n+\t\tif (arg_val[0])\n+\t\t\tgoto error;\n+\n+\t\t/* Endianness conversion. */\n+\t\tif (arg->is_network_byte_order)\n+\t\t\tval = field_hton(val, arg->n_bits);\n+\n+\t\t/* Copy to entry. */\n+\t\tmemcpy(&entry->action_data[arg_offset],\n+\t\t       (uint8_t *)&val,\n+\t\t       arg->n_bits / 8);\n+\n+\t\targ_offset += arg->n_bits / 8;\n+\t}\n+\n+\ttokens += 2 + action->info.n_args * 2;\n+\tn_tokens -= 2 + action->info.n_args * 2;\n+\n+other:\n+\tif (n_tokens)\n+\t\tgoto error;\n+\n+\tfree(s0);\n+\treturn entry;\n+\n+error:\n+\ttable_entry_free(entry);\n+\tfree(s0);\n+\tif (is_blank_or_comment)\n+\t\t*is_blank_or_comment = blank_or_comment;\n+\treturn NULL;\n+}\n+\n static void\n table_entry_printf(FILE *f,\n \t\t   struct rte_swx_ctl_pipeline *ctl,\ndiff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h\nindex f37301cf95..2a7d1d57ce 100644\n--- a/lib/pipeline/rte_swx_ctl.h\n+++ b/lib/pipeline/rte_swx_ctl.h\n@@ -52,6 +52,9 @@ struct rte_swx_ctl_pipeline_info {\n \t/** Number of selector tables. */\n \tuint32_t n_selectors;\n \n+\t/** Number of learner tables. */\n+\tuint32_t n_learners;\n+\n \t/** Number of register arrays. */\n \tuint32_t n_regarrays;\n \n@@ -512,6 +515,142 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,\n \t\t\t\t\t const char *selector_name,\n \t\t\t\t\t struct rte_swx_pipeline_selector_stats *stats);\n \n+/*\n+ * Learner Table Query API.\n+ */\n+\n+/** Learner table info. */\n+struct rte_swx_ctl_learner_info {\n+\t/** Learner table name. */\n+\tchar name[RTE_SWX_CTL_NAME_SIZE];\n+\n+\t/** Number of match fields. */\n+\tuint32_t n_match_fields;\n+\n+\t/** Number of actions. */\n+\tuint32_t n_actions;\n+\n+\t/** Non-zero (true) when the default action is constant, therefore it\n+\t * cannot be changed; zero (false) when the default action not constant,\n+\t * therefore it can be changed.\n+\t */\n+\tint default_action_is_const;\n+\n+\t/** Learner table size parameter. */\n+\tuint32_t size;\n+};\n+\n+/**\n+ * Learner table info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] learner_id\n+ *   Learner table ID (0 .. *n_learners* - 1).\n+ * @param[out] learner\n+ *   Learner table info.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,\n+\t\t\t     uint32_t learner_id,\n+\t\t\t     struct rte_swx_ctl_learner_info *learner);\n+\n+/**\n+ * Learner table match field info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] learner_id\n+ *   Learner table ID (0 .. *n_learners* - 1).\n+ * @param[in] match_field_id\n+ *   Match field ID (0 .. *n_match_fields* - 1).\n+ * @param[out] match_field\n+ *   Learner table match field info.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t\t uint32_t learner_id,\n+\t\t\t\t\t uint32_t match_field_id,\n+\t\t\t\t\t struct rte_swx_ctl_table_match_field_info *match_field);\n+\n+/**\n+ * Learner table action info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] learner_id\n+ *   Learner table ID (0 .. *n_learners* - 1).\n+ * @param[in] learner_action_id\n+ *   Action index within the set of learner table actions (0 .. learner table n_actions - 1). Not\n+ *   to be confused with the pipeline-leve action ID (0 .. pipeline n_actions - 1), which is\n+ *   precisely what this function returns as part of the *learner_action*.\n+ * @param[out] learner_action\n+ *   Learner action info.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t    uint32_t learner_id,\n+\t\t\t\t    uint32_t learner_action_id,\n+\t\t\t\t    struct rte_swx_ctl_table_action_info *learner_action);\n+\n+/** Learner table statistics. */\n+struct rte_swx_learner_stats {\n+\t/** Number of packets with lookup hit. */\n+\tuint64_t n_pkts_hit;\n+\n+\t/** Number of packets with lookup miss. */\n+\tuint64_t n_pkts_miss;\n+\n+\t/** Number of packets with successful learning. */\n+\tuint64_t n_pkts_learn_ok;\n+\n+\t/** Number of packets with learning error. */\n+\tuint64_t n_pkts_learn_err;\n+\n+\t/** Number of packets with forget event. */\n+\tuint64_t n_pkts_forget;\n+\n+\t/** Number of packets (with either lookup hit or miss) per pipeline action. Array of\n+\t * pipeline *n_actions* elements indedex by the pipeline-level *action_id*, therefore this\n+\t * array has the same size for all the tables within the same pipeline.\n+\t */\n+\tuint64_t *n_pkts_action;\n+};\n+\n+/**\n+ * Learner table statistics counters read\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] learner_name\n+ *   Learner table name.\n+ * @param[out] stats\n+ *   Learner table stats. Must point to a pre-allocated structure. The *n_pkts_action* field also\n+ *   needs to be pre-allocated as array of pipeline *n_actions* elements. The pipeline actions that\n+ *   are not valid for the current learner table have their associated *n_pkts_action* element\n+ *   always set to zero.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,\n+\t\t\t\t      const char *learner_name,\n+\t\t\t\t      struct rte_swx_learner_stats *stats);\n+\n /*\n  * Table Update API.\n  */\n@@ -761,6 +900,27 @@ rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *c\n \t\t\t\t\t\t  uint32_t group_id,\n \t\t\t\t\t\t  uint32_t member_id);\n \n+/**\n+ * Pipeline learner table default entry add\n+ *\n+ * Schedule learner table default entry update as part of the next commit operation.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] learner_name\n+ *   Learner table name.\n+ * @param[in] entry\n+ *   The new table default entry. The *key* and *key_mask* entry fields are ignored.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t       const char *learner_name,\n+\t\t\t\t\t       struct rte_swx_table_entry *entry);\n+\n /**\n  * Pipeline commit\n  *\n@@ -819,6 +979,31 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,\n \t\t\t\t      const char *string,\n \t\t\t\t      int *is_blank_or_comment);\n \n+/**\n+ * Pipeline learner table default entry read\n+ *\n+ * Read learner table default entry from string.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] learner_name\n+ *   Learner table name.\n+ * @param[in] string\n+ *   String containing the learner table default entry.\n+ * @param[out] is_blank_or_comment\n+ *   On error, this argument provides an indication of whether *string* contains\n+ *   an invalid table entry (set to zero) or a blank or comment line that should\n+ *   typically be ignored (set to a non-zero value).\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument.\n+ */\n+struct rte_swx_table_entry *\n+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t\tconst char *learner_name,\n+\t\t\t\t\t\tconst char *string,\n+\t\t\t\t\t\tint *is_blank_or_comment);\n+\n /**\n  * Pipeline table print to file\n  *\ndiff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c\nindex 13028bcc6a..a85d80289d 100644\n--- a/lib/pipeline/rte_swx_pipeline.c\n+++ b/lib/pipeline/rte_swx_pipeline.c\n@@ -16,6 +16,7 @@\n #include <rte_meter.h>\n \n #include <rte_swx_table_selector.h>\n+#include <rte_swx_table_learner.h>\n \n #include \"rte_swx_pipeline.h\"\n #include \"rte_swx_ctl.h\"\n@@ -511,6 +512,13 @@ enum instruction_type {\n \t/* table TABLE */\n \tINSTR_TABLE,\n \tINSTR_SELECTOR,\n+\tINSTR_LEARNER,\n+\n+\t/* learn LEARNER ACTION_NAME */\n+\tINSTR_LEARNER_LEARN,\n+\n+\t/* forget */\n+\tINSTR_LEARNER_FORGET,\n \n \t/* extern e.obj.func */\n \tINSTR_EXTERN_OBJ,\n@@ -636,6 +644,10 @@ struct instr_table {\n \tuint8_t table_id;\n };\n \n+struct instr_learn {\n+\tuint8_t action_id;\n+};\n+\n struct instr_extern_obj {\n \tuint8_t ext_obj_id;\n \tuint8_t func_id;\n@@ -726,6 +738,7 @@ struct instruction {\n \t\tstruct instr_dma dma;\n \t\tstruct instr_dst_src alu;\n \t\tstruct instr_table table;\n+\t\tstruct instr_learn learn;\n \t\tstruct instr_extern_obj ext_obj;\n \t\tstruct instr_extern_func ext_func;\n \t\tstruct instr_jmp jmp;\n@@ -746,7 +759,7 @@ struct action {\n \tTAILQ_ENTRY(action) node;\n \tchar name[RTE_SWX_NAME_SIZE];\n \tstruct struct_type *st;\n-\tint *args_endianness; /* 0 = Host Byte Order (HBO). */\n+\tint *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */\n \tstruct instruction *instructions;\n \tuint32_t n_instructions;\n \tuint32_t id;\n@@ -839,6 +852,47 @@ struct selector_statistics {\n \tuint64_t n_pkts;\n };\n \n+/*\n+ * Learner table.\n+ */\n+struct learner {\n+\tTAILQ_ENTRY(learner) node;\n+\tchar name[RTE_SWX_NAME_SIZE];\n+\n+\t/* Match. */\n+\tstruct field **fields;\n+\tuint32_t n_fields;\n+\tstruct header *header;\n+\n+\t/* Action. */\n+\tstruct action **actions;\n+\tstruct field **action_arg;\n+\tstruct action *default_action;\n+\tuint8_t *default_action_data;\n+\tuint32_t n_actions;\n+\tint default_action_is_const;\n+\tuint32_t action_data_size_max;\n+\n+\tuint32_t size;\n+\tuint32_t timeout;\n+\tuint32_t id;\n+};\n+\n+TAILQ_HEAD(learner_tailq, learner);\n+\n+struct learner_runtime {\n+\tvoid *mailbox;\n+\tuint8_t **key;\n+\tuint8_t **action_data;\n+};\n+\n+struct learner_statistics {\n+\tuint64_t n_pkts_hit[2]; /* 0 = Miss, 1 = Hit. */\n+\tuint64_t n_pkts_learn[2]; /* 0 = Learn OK, 1 = Learn error. */\n+\tuint64_t n_pkts_forget;\n+\tuint64_t *n_pkts_action;\n+};\n+\n /*\n  * Register array.\n  */\n@@ -919,9 +973,12 @@ struct thread {\n \t/* Tables. */\n \tstruct table_runtime *tables;\n \tstruct selector_runtime *selectors;\n+\tstruct learner_runtime *learners;\n \tstruct rte_swx_table_state *table_state;\n \tuint64_t action_id;\n \tint hit; /* 0 = Miss, 1 = Hit. */\n+\tuint32_t learner_id;\n+\tuint64_t time;\n \n \t/* Extern objects and functions. */\n \tstruct extern_obj_runtime *extern_objs;\n@@ -1355,6 +1412,7 @@ struct rte_swx_pipeline {\n \tstruct table_type_tailq table_types;\n \tstruct table_tailq tables;\n \tstruct selector_tailq selectors;\n+\tstruct learner_tailq learners;\n \tstruct regarray_tailq regarrays;\n \tstruct meter_profile_tailq meter_profiles;\n \tstruct metarray_tailq metarrays;\n@@ -1365,6 +1423,7 @@ struct rte_swx_pipeline {\n \tstruct rte_swx_table_state *table_state;\n \tstruct table_statistics *table_stats;\n \tstruct selector_statistics *selector_stats;\n+\tstruct learner_statistics *learner_stats;\n \tstruct regarray_runtime *regarray_runtime;\n \tstruct metarray_runtime *metarray_runtime;\n \tstruct instruction *instructions;\n@@ -1378,6 +1437,7 @@ struct rte_swx_pipeline {\n \tuint32_t n_actions;\n \tuint32_t n_tables;\n \tuint32_t n_selectors;\n+\tuint32_t n_learners;\n \tuint32_t n_regarrays;\n \tuint32_t n_metarrays;\n \tuint32_t n_headers;\n@@ -3625,6 +3685,9 @@ table_find(struct rte_swx_pipeline *p, const char *name);\n static struct selector *\n selector_find(struct rte_swx_pipeline *p, const char *name);\n \n+static struct learner *\n+learner_find(struct rte_swx_pipeline *p, const char *name);\n+\n static int\n instr_table_translate(struct rte_swx_pipeline *p,\n \t\t      struct action *action,\n@@ -3635,6 +3698,7 @@ instr_table_translate(struct rte_swx_pipeline *p,\n {\n \tstruct table *t;\n \tstruct selector *s;\n+\tstruct learner *l;\n \n \tCHECK(!action, EINVAL);\n \tCHECK(n_tokens == 2, EINVAL);\n@@ -3653,6 +3717,13 @@ instr_table_translate(struct rte_swx_pipeline *p,\n \t\treturn 0;\n \t}\n \n+\tl = learner_find(p, tokens[1]);\n+\tif (l) {\n+\t\tinstr->type = INSTR_LEARNER;\n+\t\tinstr->table.table_id = l->id;\n+\t\treturn 0;\n+\t}\n+\n \tCHECK(0, EINVAL);\n }\n \n@@ -3746,6 +3817,168 @@ instr_selector_exec(struct rte_swx_pipeline *p)\n \tthread_ip_inc(p);\n }\n \n+static inline void\n+instr_learner_exec(struct rte_swx_pipeline *p)\n+{\n+\tstruct thread *t = &p->threads[p->thread_id];\n+\tstruct instruction *ip = t->ip;\n+\tuint32_t learner_id = ip->table.table_id;\n+\tstruct rte_swx_table_state *ts = &t->table_state[p->n_tables +\n+\t\tp->n_selectors + learner_id];\n+\tstruct learner_runtime *l = &t->learners[learner_id];\n+\tstruct learner_statistics *stats = &p->learner_stats[learner_id];\n+\tuint64_t action_id, n_pkts_hit, n_pkts_action, time;\n+\tuint8_t *action_data;\n+\tint done, hit;\n+\n+\t/* Table. */\n+\ttime = rte_get_tsc_cycles();\n+\n+\tdone = rte_swx_table_learner_lookup(ts->obj,\n+\t\t\t\t\t    l->mailbox,\n+\t\t\t\t\t    time,\n+\t\t\t\t\t    l->key,\n+\t\t\t\t\t    &action_id,\n+\t\t\t\t\t    &action_data,\n+\t\t\t\t\t    &hit);\n+\tif (!done) {\n+\t\t/* Thread. */\n+\t\tTRACE(\"[Thread %2u] learner %u (not finalized)\\n\",\n+\t\t      p->thread_id,\n+\t\t      learner_id);\n+\n+\t\tthread_yield(p);\n+\t\treturn;\n+\t}\n+\n+\taction_id = hit ? action_id : ts->default_action_id;\n+\taction_data = hit ? action_data : ts->default_action_data;\n+\tn_pkts_hit = stats->n_pkts_hit[hit];\n+\tn_pkts_action = stats->n_pkts_action[action_id];\n+\n+\tTRACE(\"[Thread %2u] learner %u (%s, action %u)\\n\",\n+\t      p->thread_id,\n+\t      learner_id,\n+\t      hit ? \"hit\" : \"miss\",\n+\t      (uint32_t)action_id);\n+\n+\tt->action_id = action_id;\n+\tt->structs[0] = action_data;\n+\tt->hit = hit;\n+\tt->learner_id = learner_id;\n+\tt->time = time;\n+\tstats->n_pkts_hit[hit] = n_pkts_hit + 1;\n+\tstats->n_pkts_action[action_id] = n_pkts_action + 1;\n+\n+\t/* Thread. */\n+\tthread_ip_action_call(p, t, action_id);\n+}\n+\n+/*\n+ * learn.\n+ */\n+static struct action *\n+action_find(struct rte_swx_pipeline *p, const char *name);\n+\n+static int\n+action_has_nbo_args(struct action *a);\n+\n+static int\n+instr_learn_translate(struct rte_swx_pipeline *p,\n+\t\t      struct action *action,\n+\t\t      char **tokens,\n+\t\t      int n_tokens,\n+\t\t      struct instruction *instr,\n+\t\t      struct instruction_data *data __rte_unused)\n+{\n+\tstruct action *a;\n+\n+\tCHECK(action, EINVAL);\n+\tCHECK(n_tokens == 2, EINVAL);\n+\n+\ta = action_find(p, tokens[1]);\n+\tCHECK(a, EINVAL);\n+\tCHECK(!action_has_nbo_args(a), EINVAL);\n+\n+\tinstr->type = INSTR_LEARNER_LEARN;\n+\tinstr->learn.action_id = a->id;\n+\n+\treturn 0;\n+}\n+\n+static inline void\n+instr_learn_exec(struct rte_swx_pipeline *p)\n+{\n+\tstruct thread *t = &p->threads[p->thread_id];\n+\tstruct instruction *ip = t->ip;\n+\tuint64_t action_id = ip->learn.action_id;\n+\tuint32_t learner_id = t->learner_id;\n+\tstruct rte_swx_table_state *ts = &t->table_state[p->n_tables +\n+\t\tp->n_selectors + learner_id];\n+\tstruct learner_runtime *l = &t->learners[learner_id];\n+\tstruct learner_statistics *stats = &p->learner_stats[learner_id];\n+\tuint32_t status;\n+\n+\t/* Table. */\n+\tstatus = rte_swx_table_learner_add(ts->obj,\n+\t\t\t\t\t   l->mailbox,\n+\t\t\t\t\t   t->time,\n+\t\t\t\t\t   action_id,\n+\t\t\t\t\t   l->action_data[action_id]);\n+\n+\tTRACE(\"[Thread %2u] learner %u learn %s\\n\",\n+\t      p->thread_id,\n+\t      learner_id,\n+\t      status ? \"ok\" : \"error\");\n+\n+\tstats->n_pkts_learn[status] += 1;\n+\n+\t/* Thread. */\n+\tthread_ip_inc(p);\n+}\n+\n+/*\n+ * forget.\n+ */\n+static int\n+instr_forget_translate(struct rte_swx_pipeline *p __rte_unused,\n+\t\t       struct action *action,\n+\t\t       char **tokens __rte_unused,\n+\t\t       int n_tokens,\n+\t\t       struct instruction *instr,\n+\t\t       struct instruction_data *data __rte_unused)\n+{\n+\tCHECK(action, EINVAL);\n+\tCHECK(n_tokens == 1, EINVAL);\n+\n+\tinstr->type = INSTR_LEARNER_FORGET;\n+\n+\treturn 0;\n+}\n+\n+static inline void\n+instr_forget_exec(struct rte_swx_pipeline *p)\n+{\n+\tstruct thread *t = &p->threads[p->thread_id];\n+\tuint32_t learner_id = t->learner_id;\n+\tstruct rte_swx_table_state *ts = &t->table_state[p->n_tables +\n+\t\tp->n_selectors + learner_id];\n+\tstruct learner_runtime *l = &t->learners[learner_id];\n+\tstruct learner_statistics *stats = &p->learner_stats[learner_id];\n+\n+\t/* Table. */\n+\trte_swx_table_learner_delete(ts->obj, l->mailbox);\n+\n+\tTRACE(\"[Thread %2u] learner %u forget\\n\",\n+\t      p->thread_id,\n+\t      learner_id);\n+\n+\tstats->n_pkts_forget += 1;\n+\n+\t/* Thread. */\n+\tthread_ip_inc(p);\n+}\n+\n /*\n  * extern.\n  */\n@@ -7159,9 +7392,6 @@ instr_meter_imi_exec(struct rte_swx_pipeline *p)\n /*\n  * jmp.\n  */\n-static struct action *\n-action_find(struct rte_swx_pipeline *p, const char *name);\n-\n static int\n instr_jmp_translate(struct rte_swx_pipeline *p __rte_unused,\n \t\t    struct action *action __rte_unused,\n@@ -8136,6 +8366,22 @@ instr_translate(struct rte_swx_pipeline *p,\n \t\t\t\t\t     instr,\n \t\t\t\t\t     data);\n \n+\tif (!strcmp(tokens[tpos], \"learn\"))\n+\t\treturn instr_learn_translate(p,\n+\t\t\t\t\t     action,\n+\t\t\t\t\t     &tokens[tpos],\n+\t\t\t\t\t     n_tokens - tpos,\n+\t\t\t\t\t     instr,\n+\t\t\t\t\t     data);\n+\n+\tif (!strcmp(tokens[tpos], \"forget\"))\n+\t\treturn instr_forget_translate(p,\n+\t\t\t\t\t      action,\n+\t\t\t\t\t      &tokens[tpos],\n+\t\t\t\t\t      n_tokens - tpos,\n+\t\t\t\t\t      instr,\n+\t\t\t\t\t      data);\n+\n \tif (!strcmp(tokens[tpos], \"extern\"))\n \t\treturn instr_extern_translate(p,\n \t\t\t\t\t      action,\n@@ -9096,6 +9342,9 @@ static instr_exec_t instruction_table[] = {\n \n \t[INSTR_TABLE] = instr_table_exec,\n \t[INSTR_SELECTOR] = instr_selector_exec,\n+\t[INSTR_LEARNER] = instr_learner_exec,\n+\t[INSTR_LEARNER_LEARN] = instr_learn_exec,\n+\t[INSTR_LEARNER_FORGET] = instr_forget_exec,\n \t[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,\n \t[INSTR_EXTERN_FUNC] = instr_extern_func_exec,\n \n@@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name)\n \treturn action_field_find(action, &name[2]);\n }\n \n+static int\n+action_has_nbo_args(struct action *a)\n+{\n+\tuint32_t i;\n+\n+\t/* Return if the action does not have any args. */\n+\tif (!a->st)\n+\t\treturn 0; /* FALSE */\n+\n+\tfor (i = 0; i < a->st->n_fields; i++)\n+\t\tif (a->args_endianness[i])\n+\t\t\treturn 1; /* TRUE */\n+\n+\treturn 0; /* FALSE */\n+}\n+\n+static int\n+action_does_learning(struct action *a)\n+{\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < a->n_instructions; i++)\n+\t\tswitch (a->instructions[i].type) {\n+\t\tcase INSTR_LEARNER_LEARN:\n+\t\t\treturn 1; /* TRUE */\n+\n+\t\tcase INSTR_LEARNER_FORGET:\n+\t\t\treturn 1; /* TRUE */\n+\n+\t\tdefault:\n+\t\t\tcontinue;\n+\t\t}\n+\n+\treturn 0; /* FALSE */\n+}\n+\n int\n rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,\n \t\t\t       const char *name,\n@@ -9546,6 +9831,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,\n \tCHECK_NAME(name, EINVAL);\n \tCHECK(!table_find(p, name), EEXIST);\n \tCHECK(!selector_find(p, name), EEXIST);\n+\tCHECK(!learner_find(p, name), EEXIST);\n \n \tCHECK(params, EINVAL);\n \n@@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,\n \n \t\ta = action_find(p, action_name);\n \t\tCHECK(a, EINVAL);\n+\t\tCHECK(!action_does_learning(a), EINVAL);\n \n \t\taction_data_size = a->st ? a->st->n_bits / 8 : 0;\n \t\tif (action_data_size > action_data_size_max)\n@@ -9964,6 +10251,7 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,\n \tCHECK_NAME(name, EINVAL);\n \tCHECK(!table_find(p, name), EEXIST);\n \tCHECK(!selector_find(p, name), EEXIST);\n+\tCHECK(!learner_find(p, name), EEXIST);\n \n \tCHECK(params, EINVAL);\n \n@@ -10221,73 +10509,604 @@ selector_free(struct rte_swx_pipeline *p)\n }\n \n /*\n- * Table state.\n+ * Learner table.\n  */\n-static int\n-table_state_build(struct rte_swx_pipeline *p)\n+static struct learner *\n+learner_find(struct rte_swx_pipeline *p, const char *name)\n {\n-\tstruct table *table;\n-\tstruct selector *s;\n-\n-\tp->table_state = calloc(p->n_tables + p->n_selectors,\n-\t\t\t\tsizeof(struct rte_swx_table_state));\n-\tCHECK(p->table_state, ENOMEM);\n+\tstruct learner *l;\n \n-\tTAILQ_FOREACH(table, &p->tables, node) {\n-\t\tstruct rte_swx_table_state *ts = &p->table_state[table->id];\n+\tTAILQ_FOREACH(l, &p->learners, node)\n+\t\tif (!strcmp(l->name, name))\n+\t\t\treturn l;\n \n-\t\tif (table->type) {\n-\t\t\tstruct rte_swx_table_params *params;\n+\treturn NULL;\n+}\n \n-\t\t\t/* ts->obj. */\n-\t\t\tparams = table_params_get(table);\n-\t\t\tCHECK(params, ENOMEM);\n+static struct learner *\n+learner_find_by_id(struct rte_swx_pipeline *p, uint32_t id)\n+{\n+\tstruct learner *l = NULL;\n \n-\t\t\tts->obj = table->type->ops.create(params,\n-\t\t\t\tNULL,\n-\t\t\t\ttable->args,\n-\t\t\t\tp->numa_node);\n+\tTAILQ_FOREACH(l, &p->learners, node)\n+\t\tif (l->id == id)\n+\t\t\treturn l;\n \n-\t\t\ttable_params_free(params);\n-\t\t\tCHECK(ts->obj, ENODEV);\n-\t\t}\n+\treturn NULL;\n+}\n \n-\t\t/* ts->default_action_data. */\n-\t\tif (table->action_data_size_max) {\n-\t\t\tts->default_action_data =\n-\t\t\t\tmalloc(table->action_data_size_max);\n-\t\t\tCHECK(ts->default_action_data, ENOMEM);\n+static int\n+learner_match_fields_check(struct rte_swx_pipeline *p,\n+\t\t\t   struct rte_swx_pipeline_learner_params *params,\n+\t\t\t   struct header **header)\n+{\n+\tstruct header *h0 = NULL;\n+\tstruct field *hf, *mf;\n+\tuint32_t i;\n \n-\t\t\tmemcpy(ts->default_action_data,\n-\t\t\t       table->default_action_data,\n-\t\t\t       table->action_data_size_max);\n-\t\t}\n+\t/* Return if no match fields. */\n+\tif (!params->n_fields || !params->field_names)\n+\t\treturn -EINVAL;\n \n-\t\t/* ts->default_action_id. */\n-\t\tts->default_action_id = table->default_action->id;\n-\t}\n+\t/* Check that all the match fields either belong to the same header\n+\t * or are all meta-data fields.\n+\t */\n+\thf = header_field_parse(p, params->field_names[0], &h0);\n+\tmf = metadata_field_parse(p, params->field_names[0]);\n+\tif (!hf && !mf)\n+\t\treturn -EINVAL;\n \n-\tTAILQ_FOREACH(s, &p->selectors, node) {\n-\t\tstruct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];\n-\t\tstruct rte_swx_table_selector_params *params;\n+\tfor (i = 1; i < params->n_fields; i++)\n+\t\tif (h0) {\n+\t\t\tstruct header *h;\n \n-\t\t/* ts->obj. */\n-\t\tparams = selector_table_params_get(s);\n-\t\tCHECK(params, ENOMEM);\n+\t\t\thf = header_field_parse(p, params->field_names[i], &h);\n+\t\t\tif (!hf || (h->id != h0->id))\n+\t\t\t\treturn -EINVAL;\n+\t\t} else {\n+\t\t\tmf = metadata_field_parse(p, params->field_names[i]);\n+\t\t\tif (!mf)\n+\t\t\t\treturn -EINVAL;\n+\t\t}\n \n-\t\tts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);\n+\t/* Check that there are no duplicated match fields. */\n+\tfor (i = 0; i < params->n_fields; i++) {\n+\t\tconst char *field_name = params->field_names[i];\n+\t\tuint32_t j;\n \n-\t\tselector_params_free(params);\n-\t\tCHECK(ts->obj, ENODEV);\n+\t\tfor (j = i + 1; j < params->n_fields; j++)\n+\t\t\tif (!strcmp(params->field_names[j], field_name))\n+\t\t\t\treturn -EINVAL;\n \t}\n \n+\t/* Return. */\n+\tif (header)\n+\t\t*header = h0;\n+\n \treturn 0;\n }\n \n-static void\n-table_state_build_free(struct rte_swx_pipeline *p)\n+static int\n+learner_action_args_check(struct rte_swx_pipeline *p, struct action *a, const char *mf_name)\n {\n-\tuint32_t i;\n+\tstruct struct_type *mst = p->metadata_st, *ast = a->st;\n+\tstruct field *mf, *af;\n+\tuint32_t mf_pos, i;\n+\n+\tif (!ast) {\n+\t\tif (mf_name)\n+\t\t\treturn -EINVAL;\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Check that mf_name is the name of a valid meta-data field. */\n+\tCHECK_NAME(mf_name, EINVAL);\n+\tmf = metadata_field_parse(p, mf_name);\n+\tCHECK(mf, EINVAL);\n+\n+\t/* Check that there are enough meta-data fields, starting with the mf_name field, to cover\n+\t * all the action arguments.\n+\t */\n+\tmf_pos = mf - mst->fields;\n+\tCHECK(mst->n_fields - mf_pos >= ast->n_fields, EINVAL);\n+\n+\t/* Check that the size of each of the identified meta-data fields matches exactly the size\n+\t * of the corresponding action argument.\n+\t */\n+\tfor (i = 0; i < ast->n_fields; i++) {\n+\t\tmf = &mst->fields[mf_pos + i];\n+\t\taf = &ast->fields[i];\n+\n+\t\tCHECK(mf->n_bits == af->n_bits, EINVAL);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_action_learning_check(struct rte_swx_pipeline *p,\n+\t\t\t      struct action *action,\n+\t\t\t      const char **action_names,\n+\t\t\t      uint32_t n_actions)\n+{\n+\tuint32_t i;\n+\n+\t/* For each \"learn\" instruction of the current action, check that the learned action (i.e.\n+\t * the action passed as argument to the \"learn\" instruction) is also enabled for the\n+\t * current learner table.\n+\t */\n+\tfor (i = 0; i < action->n_instructions; i++) {\n+\t\tstruct instruction *instr = &action->instructions[i];\n+\t\tuint32_t found = 0, j;\n+\n+\t\tif (instr->type != INSTR_LEARNER_LEARN)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; j < n_actions; j++) {\n+\t\t\tstruct action *a;\n+\n+\t\t\ta = action_find(p, action_names[j]);\n+\t\t\tif (!a)\n+\t\t\t\treturn -EINVAL;\n+\n+\t\t\tif (a->id == instr->learn.action_id)\n+\t\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found)\n+\t\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,\n+\t\t\t      const char *name,\n+\t\t\t      struct rte_swx_pipeline_learner_params *params,\n+\t\t\t      uint32_t size,\n+\t\t\t      uint32_t timeout)\n+{\n+\tstruct learner *l = NULL;\n+\tstruct action *default_action;\n+\tstruct header *header = NULL;\n+\tuint32_t action_data_size_max = 0, i;\n+\tint status = 0;\n+\n+\tCHECK(p, EINVAL);\n+\n+\tCHECK_NAME(name, EINVAL);\n+\tCHECK(!table_find(p, name), EEXIST);\n+\tCHECK(!selector_find(p, name), EEXIST);\n+\tCHECK(!learner_find(p, name), EEXIST);\n+\n+\tCHECK(params, EINVAL);\n+\n+\t/* Match checks. */\n+\tstatus = learner_match_fields_check(p, params, &header);\n+\tif (status)\n+\t\treturn status;\n+\n+\t/* Action checks. */\n+\tCHECK(params->n_actions, EINVAL);\n+\n+\tCHECK(params->action_names, EINVAL);\n+\tfor (i = 0; i < params->n_actions; i++) {\n+\t\tconst char *action_name = params->action_names[i];\n+\t\tconst char *action_field_name = params->action_field_names[i];\n+\t\tstruct action *a;\n+\t\tuint32_t action_data_size;\n+\n+\t\tCHECK_NAME(action_name, EINVAL);\n+\n+\t\ta = action_find(p, action_name);\n+\t\tCHECK(a, EINVAL);\n+\n+\t\tstatus = learner_action_args_check(p, a, action_field_name);\n+\t\tif (status)\n+\t\t\treturn status;\n+\n+\t\tstatus = learner_action_learning_check(p,\n+\t\t\t\t\t\t       a,\n+\t\t\t\t\t\t       params->action_names,\n+\t\t\t\t\t\t       params->n_actions);\n+\t\tif (status)\n+\t\t\treturn status;\n+\n+\t\taction_data_size = a->st ? a->st->n_bits / 8 : 0;\n+\t\tif (action_data_size > action_data_size_max)\n+\t\t\taction_data_size_max = action_data_size;\n+\t}\n+\n+\tCHECK_NAME(params->default_action_name, EINVAL);\n+\tfor (i = 0; i < p->n_actions; i++)\n+\t\tif (!strcmp(params->action_names[i],\n+\t\t\t    params->default_action_name))\n+\t\t\tbreak;\n+\tCHECK(i < params->n_actions, EINVAL);\n+\n+\tdefault_action = action_find(p, params->default_action_name);\n+\tCHECK((default_action->st && params->default_action_data) ||\n+\t      !params->default_action_data, EINVAL);\n+\n+\t/* Any other checks. */\n+\tCHECK(size, EINVAL);\n+\tCHECK(timeout, EINVAL);\n+\n+\t/* Memory allocation. */\n+\tl = calloc(1, sizeof(struct learner));\n+\tif (!l)\n+\t\tgoto nomem;\n+\n+\tl->fields = calloc(params->n_fields, sizeof(struct field *));\n+\tif (!l->fields)\n+\t\tgoto nomem;\n+\n+\tl->actions = calloc(params->n_actions, sizeof(struct action *));\n+\tif (!l->actions)\n+\t\tgoto nomem;\n+\n+\tl->action_arg = calloc(params->n_actions, sizeof(struct field *));\n+\tif (!l->action_arg)\n+\t\tgoto nomem;\n+\n+\tif (action_data_size_max) {\n+\t\tl->default_action_data = calloc(1, action_data_size_max);\n+\t\tif (!l->default_action_data)\n+\t\t\tgoto nomem;\n+\t}\n+\n+\t/* Node initialization. */\n+\tstrcpy(l->name, name);\n+\n+\tfor (i = 0; i < params->n_fields; i++) {\n+\t\tconst char *field_name = params->field_names[i];\n+\n+\t\tl->fields[i] = header ?\n+\t\t\theader_field_parse(p, field_name, NULL) :\n+\t\t\tmetadata_field_parse(p, field_name);\n+\t}\n+\n+\tl->n_fields = params->n_fields;\n+\n+\tl->header = header;\n+\n+\tfor (i = 0; i < params->n_actions; i++) {\n+\t\tconst char *mf_name = params->action_field_names[i];\n+\n+\t\tl->actions[i] = action_find(p, params->action_names[i]);\n+\n+\t\tl->action_arg[i] = mf_name ? metadata_field_parse(p, mf_name) : NULL;\n+\t}\n+\n+\tl->default_action = default_action;\n+\n+\tif (default_action->st)\n+\t\tmemcpy(l->default_action_data,\n+\t\t       params->default_action_data,\n+\t\t       default_action->st->n_bits / 8);\n+\n+\tl->n_actions = params->n_actions;\n+\n+\tl->default_action_is_const = params->default_action_is_const;\n+\n+\tl->action_data_size_max = action_data_size_max;\n+\n+\tl->size = size;\n+\n+\tl->timeout = timeout;\n+\n+\tl->id = p->n_learners;\n+\n+\t/* Node add to tailq. */\n+\tTAILQ_INSERT_TAIL(&p->learners, l, node);\n+\tp->n_learners++;\n+\n+\treturn 0;\n+\n+nomem:\n+\tif (!l)\n+\t\treturn -ENOMEM;\n+\n+\tfree(l->action_arg);\n+\tfree(l->actions);\n+\tfree(l->fields);\n+\tfree(l);\n+\n+\treturn -ENOMEM;\n+}\n+\n+static void\n+learner_params_free(struct rte_swx_table_learner_params *params)\n+{\n+\tif (!params)\n+\t\treturn;\n+\n+\tfree(params->key_mask0);\n+\n+\tfree(params);\n+}\n+\n+static struct rte_swx_table_learner_params *\n+learner_params_get(struct learner *l)\n+{\n+\tstruct rte_swx_table_learner_params *params = NULL;\n+\tstruct field *first, *last;\n+\tuint32_t i;\n+\n+\t/* Memory allocation. */\n+\tparams = calloc(1, sizeof(struct rte_swx_table_learner_params));\n+\tif (!params)\n+\t\tgoto error;\n+\n+\t/* Find first (smallest offset) and last (biggest offset) match fields. */\n+\tfirst = l->fields[0];\n+\tlast = l->fields[0];\n+\n+\tfor (i = 0; i < l->n_fields; i++) {\n+\t\tstruct field *f = l->fields[i];\n+\n+\t\tif (f->offset < first->offset)\n+\t\t\tfirst = f;\n+\n+\t\tif (f->offset > last->offset)\n+\t\t\tlast = f;\n+\t}\n+\n+\t/* Key offset and size. */\n+\tparams->key_offset = first->offset / 8;\n+\tparams->key_size = (last->offset + last->n_bits - first->offset) / 8;\n+\n+\t/* Memory allocation. */\n+\tparams->key_mask0 = calloc(1, params->key_size);\n+\tif (!params->key_mask0)\n+\t\tgoto error;\n+\n+\t/* Key mask. */\n+\tfor (i = 0; i < l->n_fields; i++) {\n+\t\tstruct field *f = l->fields[i];\n+\t\tuint32_t start = (f->offset - first->offset) / 8;\n+\t\tsize_t size = f->n_bits / 8;\n+\n+\t\tmemset(&params->key_mask0[start], 0xFF, size);\n+\t}\n+\n+\t/* Action data size. */\n+\tparams->action_data_size = l->action_data_size_max;\n+\n+\t/* Maximum number of keys. */\n+\tparams->n_keys_max = l->size;\n+\n+\t/* Timeout. */\n+\tparams->key_timeout = l->timeout;\n+\n+\treturn params;\n+\n+error:\n+\tlearner_params_free(params);\n+\treturn NULL;\n+}\n+\n+static void\n+learner_build_free(struct rte_swx_pipeline *p)\n+{\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {\n+\t\tstruct thread *t = &p->threads[i];\n+\t\tuint32_t j;\n+\n+\t\tif (!t->learners)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; j < p->n_learners; j++) {\n+\t\t\tstruct learner_runtime *r = &t->learners[j];\n+\n+\t\t\tfree(r->mailbox);\n+\t\t\tfree(r->action_data);\n+\t\t}\n+\n+\t\tfree(t->learners);\n+\t\tt->learners = NULL;\n+\t}\n+\n+\tif (p->learner_stats) {\n+\t\tfor (i = 0; i < p->n_learners; i++)\n+\t\t\tfree(p->learner_stats[i].n_pkts_action);\n+\n+\t\tfree(p->learner_stats);\n+\t}\n+}\n+\n+static int\n+learner_build(struct rte_swx_pipeline *p)\n+{\n+\tuint32_t i;\n+\tint status = 0;\n+\n+\t/* Per pipeline: learner statistics. */\n+\tp->learner_stats = calloc(p->n_learners, sizeof(struct learner_statistics));\n+\tCHECK(p->learner_stats, ENOMEM);\n+\n+\tfor (i = 0; i < p->n_learners; i++) {\n+\t\tp->learner_stats[i].n_pkts_action = calloc(p->n_actions, sizeof(uint64_t));\n+\t\tCHECK(p->learner_stats[i].n_pkts_action, ENOMEM);\n+\t}\n+\n+\t/* Per thread: learner run-time. */\n+\tfor (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {\n+\t\tstruct thread *t = &p->threads[i];\n+\t\tstruct learner *l;\n+\n+\t\tt->learners = calloc(p->n_learners, sizeof(struct learner_runtime));\n+\t\tif (!t->learners) {\n+\t\t\tstatus = -ENOMEM;\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tTAILQ_FOREACH(l, &p->learners, node) {\n+\t\t\tstruct learner_runtime *r = &t->learners[l->id];\n+\t\t\tuint64_t size;\n+\t\t\tuint32_t j;\n+\n+\t\t\t/* r->mailbox. */\n+\t\t\tsize = rte_swx_table_learner_mailbox_size_get();\n+\t\t\tif (size) {\n+\t\t\t\tr->mailbox = calloc(1, size);\n+\t\t\t\tif (!r->mailbox) {\n+\t\t\t\t\tstatus = -ENOMEM;\n+\t\t\t\t\tgoto error;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\t/* r->key. */\n+\t\t\tr->key = l->header ?\n+\t\t\t\t&t->structs[l->header->struct_id] :\n+\t\t\t\t&t->structs[p->metadata_struct_id];\n+\n+\t\t\t/* r->action_data. */\n+\t\t\tr->action_data = calloc(p->n_actions, sizeof(uint8_t *));\n+\t\t\tif (!r->action_data) {\n+\t\t\t\tstatus = -ENOMEM;\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n+\t\t\tfor (j = 0; j < l->n_actions; j++) {\n+\t\t\t\tstruct action *a = l->actions[j];\n+\t\t\t\tstruct field *mf = l->action_arg[j];\n+\t\t\t\tuint8_t *m = t->structs[p->metadata_struct_id];\n+\n+\t\t\t\tr->action_data[a->id] = mf ? &m[mf->offset / 8] : NULL;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tlearner_build_free(p);\n+\treturn status;\n+}\n+\n+static void\n+learner_free(struct rte_swx_pipeline *p)\n+{\n+\tlearner_build_free(p);\n+\n+\t/* Learner tables. */\n+\tfor ( ; ; ) {\n+\t\tstruct learner *l;\n+\n+\t\tl = TAILQ_FIRST(&p->learners);\n+\t\tif (!l)\n+\t\t\tbreak;\n+\n+\t\tTAILQ_REMOVE(&p->learners, l, node);\n+\t\tfree(l->fields);\n+\t\tfree(l->actions);\n+\t\tfree(l->action_arg);\n+\t\tfree(l->default_action_data);\n+\t\tfree(l);\n+\t}\n+}\n+\n+/*\n+ * Table state.\n+ */\n+static int\n+table_state_build(struct rte_swx_pipeline *p)\n+{\n+\tstruct table *table;\n+\tstruct selector *s;\n+\tstruct learner *l;\n+\n+\tp->table_state = calloc(p->n_tables + p->n_selectors,\n+\t\t\t\tsizeof(struct rte_swx_table_state));\n+\tCHECK(p->table_state, ENOMEM);\n+\n+\tTAILQ_FOREACH(table, &p->tables, node) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[table->id];\n+\n+\t\tif (table->type) {\n+\t\t\tstruct rte_swx_table_params *params;\n+\n+\t\t\t/* ts->obj. */\n+\t\t\tparams = table_params_get(table);\n+\t\t\tCHECK(params, ENOMEM);\n+\n+\t\t\tts->obj = table->type->ops.create(params,\n+\t\t\t\tNULL,\n+\t\t\t\ttable->args,\n+\t\t\t\tp->numa_node);\n+\n+\t\t\ttable_params_free(params);\n+\t\t\tCHECK(ts->obj, ENODEV);\n+\t\t}\n+\n+\t\t/* ts->default_action_data. */\n+\t\tif (table->action_data_size_max) {\n+\t\t\tts->default_action_data =\n+\t\t\t\tmalloc(table->action_data_size_max);\n+\t\t\tCHECK(ts->default_action_data, ENOMEM);\n+\n+\t\t\tmemcpy(ts->default_action_data,\n+\t\t\t       table->default_action_data,\n+\t\t\t       table->action_data_size_max);\n+\t\t}\n+\n+\t\t/* ts->default_action_id. */\n+\t\tts->default_action_id = table->default_action->id;\n+\t}\n+\n+\tTAILQ_FOREACH(s, &p->selectors, node) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];\n+\t\tstruct rte_swx_table_selector_params *params;\n+\n+\t\t/* ts->obj. */\n+\t\tparams = selector_table_params_get(s);\n+\t\tCHECK(params, ENOMEM);\n+\n+\t\tts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);\n+\n+\t\tselector_params_free(params);\n+\t\tCHECK(ts->obj, ENODEV);\n+\t}\n+\n+\tTAILQ_FOREACH(l, &p->learners, node) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[p->n_tables +\n+\t\t\tp->n_selectors + l->id];\n+\t\tstruct rte_swx_table_learner_params *params;\n+\n+\t\t/* ts->obj. */\n+\t\tparams = learner_params_get(l);\n+\t\tCHECK(params, ENOMEM);\n+\n+\t\tts->obj = rte_swx_table_learner_create(params, p->numa_node);\n+\t\tlearner_params_free(params);\n+\t\tCHECK(ts->obj, ENODEV);\n+\n+\t\t/* ts->default_action_data. */\n+\t\tif (l->action_data_size_max) {\n+\t\t\tts->default_action_data = malloc(l->action_data_size_max);\n+\t\t\tCHECK(ts->default_action_data, ENOMEM);\n+\n+\t\t\tmemcpy(ts->default_action_data,\n+\t\t\t       l->default_action_data,\n+\t\t\t       l->action_data_size_max);\n+\t\t}\n+\n+\t\t/* ts->default_action_id. */\n+\t\tts->default_action_id = l->default_action->id;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+table_state_build_free(struct rte_swx_pipeline *p)\n+{\n+\tuint32_t i;\n \n \tif (!p->table_state)\n \t\treturn;\n@@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p)\n \t\t\trte_swx_table_selector_free(ts->obj);\n \t}\n \n+\tfor (i = 0; i < p->n_learners; i++) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[p->n_tables + p->n_selectors + i];\n+\n+\t\t/* ts->obj. */\n+\t\tif (ts->obj)\n+\t\t\trte_swx_table_learner_free(ts->obj);\n+\n+\t\t/* ts->default_action_data. */\n+\t\tfree(ts->default_action_data);\n+\t}\n+\n \tfree(p->table_state);\n \tp->table_state = NULL;\n }\n@@ -10653,6 +11483,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)\n \tTAILQ_INIT(&pipeline->table_types);\n \tTAILQ_INIT(&pipeline->tables);\n \tTAILQ_INIT(&pipeline->selectors);\n+\tTAILQ_INIT(&pipeline->learners);\n \tTAILQ_INIT(&pipeline->regarrays);\n \tTAILQ_INIT(&pipeline->meter_profiles);\n \tTAILQ_INIT(&pipeline->metarrays);\n@@ -10675,6 +11506,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)\n \tmetarray_free(p);\n \tregarray_free(p);\n \ttable_state_free(p);\n+\tlearner_free(p);\n \tselector_free(p);\n \ttable_free(p);\n \taction_free(p);\n@@ -10759,6 +11591,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)\n \tif (status)\n \t\tgoto error;\n \n+\tstatus = learner_build(p);\n+\tif (status)\n+\t\tgoto error;\n+\n \tstatus = table_state_build(p);\n \tif (status)\n \t\tgoto error;\n@@ -10778,6 +11614,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)\n \tmetarray_build_free(p);\n \tregarray_build_free(p);\n \ttable_state_build_free(p);\n+\tlearner_build_free(p);\n \tselector_build_free(p);\n \ttable_build_free(p);\n \taction_build_free(p);\n@@ -10839,6 +11676,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,\n \tpipeline->n_actions = n_actions;\n \tpipeline->n_tables = n_tables;\n \tpipeline->n_selectors = p->n_selectors;\n+\tpipeline->n_learners = p->n_learners;\n \tpipeline->n_regarrays = p->n_regarrays;\n \tpipeline->n_metarrays = p->n_metarrays;\n \n@@ -11084,6 +11922,75 @@ rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,\n \treturn 0;\n }\n \n+int\n+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,\n+\t\t\t     uint32_t learner_id,\n+\t\t\t     struct rte_swx_ctl_learner_info *learner)\n+{\n+\tstruct learner *l = NULL;\n+\n+\tif (!p || !learner)\n+\t\treturn -EINVAL;\n+\n+\tl = learner_find_by_id(p, learner_id);\n+\tif (!l)\n+\t\treturn -EINVAL;\n+\n+\tstrcpy(learner->name, l->name);\n+\n+\tlearner->n_match_fields = l->n_fields;\n+\tlearner->n_actions = l->n_actions;\n+\tlearner->default_action_is_const = l->default_action_is_const;\n+\tlearner->size = l->size;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t\t uint32_t learner_id,\n+\t\t\t\t\t uint32_t match_field_id,\n+\t\t\t\t\t struct rte_swx_ctl_table_match_field_info *match_field)\n+{\n+\tstruct learner *l;\n+\tstruct field *f;\n+\n+\tif (!p || (learner_id >= p->n_learners) || !match_field)\n+\t\treturn -EINVAL;\n+\n+\tl = learner_find_by_id(p, learner_id);\n+\tif (!l || (match_field_id >= l->n_fields))\n+\t\treturn -EINVAL;\n+\n+\tf = l->fields[match_field_id];\n+\tmatch_field->match_type = RTE_SWX_TABLE_MATCH_EXACT;\n+\tmatch_field->is_header = l->header ? 1 : 0;\n+\tmatch_field->n_bits = f->n_bits;\n+\tmatch_field->offset = f->offset;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t    uint32_t learner_id,\n+\t\t\t\t    uint32_t learner_action_id,\n+\t\t\t\t    struct rte_swx_ctl_table_action_info *learner_action)\n+{\n+\tstruct learner *l;\n+\n+\tif (!p || (learner_id >= p->n_learners) || !learner_action)\n+\t\treturn -EINVAL;\n+\n+\tl = learner_find_by_id(p, learner_id);\n+\tif (!l || (learner_action_id >= l->n_actions))\n+\t\treturn -EINVAL;\n+\n+\tlearner_action->action_id = l->actions[learner_action_id]->id;\n+\n+\treturn 0;\n+}\n+\n int\n rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,\n \t\t\t\t struct rte_swx_table_state **table_state)\n@@ -11188,6 +12095,38 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,\n \treturn 0;\n }\n \n+int\n+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,\n+\t\t\t\t\tconst char *learner_name,\n+\t\t\t\t\tstruct rte_swx_learner_stats *stats)\n+{\n+\tstruct learner *l;\n+\tstruct learner_statistics *learner_stats;\n+\n+\tif (!p || !learner_name || !learner_name[0] || !stats || !stats->n_pkts_action)\n+\t\treturn -EINVAL;\n+\n+\tl = learner_find(p, learner_name);\n+\tif (!l)\n+\t\treturn -EINVAL;\n+\n+\tlearner_stats = &p->learner_stats[l->id];\n+\n+\tmemcpy(&stats->n_pkts_action,\n+\t       &learner_stats->n_pkts_action,\n+\t       p->n_actions * sizeof(uint64_t));\n+\n+\tstats->n_pkts_hit = learner_stats->n_pkts_hit[1];\n+\tstats->n_pkts_miss = learner_stats->n_pkts_hit[0];\n+\n+\tstats->n_pkts_learn_ok = learner_stats->n_pkts_learn[0];\n+\tstats->n_pkts_learn_err = learner_stats->n_pkts_learn[1];\n+\n+\tstats->n_pkts_forget = learner_stats->n_pkts_forget;\n+\n+\treturn 0;\n+}\n+\n int\n rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,\n \t\t\t      uint32_t regarray_id,\ndiff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h\nindex 5afca2bc20..2f18a820b9 100644\n--- a/lib/pipeline/rte_swx_pipeline.h\n+++ b/lib/pipeline/rte_swx_pipeline.h\n@@ -676,6 +676,83 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,\n \t\t\t\t const char *name,\n \t\t\t\t struct rte_swx_pipeline_selector_params *params);\n \n+/** Pipeline learner table parameters. */\n+struct rte_swx_pipeline_learner_params {\n+\t/** The set of match fields for the current table.\n+\t * Restriction: All the match fields of the current table need to be\n+\t * part of the same struct, i.e. either all the match fields are part of\n+\t * the same header or all the match fields are part of the meta-data.\n+\t */\n+\tconst char **field_names;\n+\n+\t/** The number of match fields for the current table. Must be non-zero.\n+\t */\n+\tuint32_t n_fields;\n+\n+\t/** The set of actions for the current table. */\n+\tconst char **action_names;\n+\n+\t/** The number of actions for the current table. Must be at least one.\n+\t */\n+\tuint32_t n_actions;\n+\n+\t/** This table type allows adding the latest lookup key (typically done\n+\t * only in the case of lookup miss) to the table with a given action.\n+\t * The action arguments are picked up from the packet meta-data: for\n+\t * each action, a set of successive meta-data fields (with the name of\n+\t * the first such field provided here) is 1:1 mapped to the action\n+\t * arguments. These meta-data fields must be set with the actual values\n+\t * of the action arguments before the key add operation.\n+\t */\n+\tconst char **action_field_names;\n+\n+\t/** The default table action that gets executed on lookup miss. Must be\n+\t * one of the table actions included in the *action_names*.\n+\t */\n+\tconst char *default_action_name;\n+\n+\t/** Default action data. The size of this array is the action data size\n+\t * of the default action. Must be NULL if the default action data size\n+\t * is zero.\n+\t */\n+\tuint8_t *default_action_data;\n+\n+\t/** If non-zero (true), then the default action of the current table\n+\t * cannot be changed. If zero (false), then the default action can be\n+\t * changed in the future with another action from the *action_names*\n+\t * list.\n+\t */\n+\tint default_action_is_const;\n+};\n+\n+/**\n+ * Pipeline learner table configure\n+ *\n+ * @param[out] p\n+ *   Pipeline handle.\n+ * @param[in] name\n+ *   Learner table name.\n+ * @param[in] params\n+ *   Learner table parameters.\n+ * @param[in] size\n+ *   The maximum number of table entries. Must be non-zero.\n+ * @param[in] timeout\n+ *   Table entry timeout in seconds. Must be non-zero.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument;\n+ *   -ENOMEM: Not enough space/cannot allocate memory;\n+ *   -EEXIST: Learner table with this name already exists;\n+ *   -ENODEV: Learner table creation error.\n+ */\n+__rte_experimental\n+int\n+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,\n+\t\t\t\tconst char *name,\n+\t\t\t\tstruct rte_swx_pipeline_learner_params *params,\n+\t\t\t\tuint32_t size,\n+\t\t\t\tuint32_t timeout);\n+\n /**\n  * Pipeline register array configure\n  *\ndiff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c\nindex c57893f18c..d9cd1d0595 100644\n--- a/lib/pipeline/rte_swx_pipeline_spec.c\n+++ b/lib/pipeline/rte_swx_pipeline_spec.c\n@@ -20,7 +20,10 @@\n #define TABLE_ACTIONS_BLOCK 4\n #define SELECTOR_BLOCK 5\n #define SELECTOR_SELECTOR_BLOCK 6\n-#define APPLY_BLOCK 7\n+#define LEARNER_BLOCK 7\n+#define LEARNER_KEY_BLOCK 8\n+#define LEARNER_ACTIONS_BLOCK 9\n+#define APPLY_BLOCK 10\n \n /*\n  * extobj.\n@@ -1281,6 +1284,420 @@ selector_block_parse(struct selector_spec *s,\n \treturn -EINVAL;\n }\n \n+/*\n+ * learner.\n+ *\n+ * learner {\n+ *\tkey {\n+ *\t\tMATCH_FIELD_NAME\n+ *\t\t...\n+ *\t}\n+ *\tactions {\n+ *\t\tACTION_NAME args METADATA_FIELD_NAME\n+ *\t\t...\n+ *\t}\n+ *\tdefault_action ACTION_NAME args none | ARGS_BYTE_ARRAY [ const ]\n+ *\tsize SIZE\n+ *\ttimeout TIMEOUT_IN_SECONDS\n+ * }\n+ */\n+struct learner_spec {\n+\tchar *name;\n+\tstruct rte_swx_pipeline_learner_params params;\n+\tuint32_t size;\n+\tuint32_t timeout;\n+};\n+\n+static void\n+learner_spec_free(struct learner_spec *s)\n+{\n+\tuintptr_t default_action_name;\n+\tuint32_t i;\n+\n+\tif (!s)\n+\t\treturn;\n+\n+\tfree(s->name);\n+\ts->name = NULL;\n+\n+\tfor (i = 0; i < s->params.n_fields; i++) {\n+\t\tuintptr_t name = (uintptr_t)s->params.field_names[i];\n+\n+\t\tfree((void *)name);\n+\t}\n+\n+\tfree(s->params.field_names);\n+\ts->params.field_names = NULL;\n+\n+\ts->params.n_fields = 0;\n+\n+\tfor (i = 0; i < s->params.n_actions; i++) {\n+\t\tuintptr_t name = (uintptr_t)s->params.action_names[i];\n+\n+\t\tfree((void *)name);\n+\t}\n+\n+\tfree(s->params.action_names);\n+\ts->params.action_names = NULL;\n+\n+\tfor (i = 0; i < s->params.n_actions; i++) {\n+\t\tuintptr_t name = (uintptr_t)s->params.action_field_names[i];\n+\n+\t\tfree((void *)name);\n+\t}\n+\n+\tfree(s->params.action_field_names);\n+\ts->params.action_field_names = NULL;\n+\n+\ts->params.n_actions = 0;\n+\n+\tdefault_action_name = (uintptr_t)s->params.default_action_name;\n+\tfree((void *)default_action_name);\n+\ts->params.default_action_name = NULL;\n+\n+\tfree(s->params.default_action_data);\n+\ts->params.default_action_data = NULL;\n+\n+\ts->params.default_action_is_const = 0;\n+\n+\ts->size = 0;\n+\n+\ts->timeout = 0;\n+}\n+\n+static int\n+learner_key_statement_parse(uint32_t *block_mask,\n+\t\t\t    char **tokens,\n+\t\t\t    uint32_t n_tokens,\n+\t\t\t    uint32_t n_lines,\n+\t\t\t    uint32_t *err_line,\n+\t\t\t    const char **err_msg)\n+{\n+\t/* Check format. */\n+\tif ((n_tokens != 2) || strcmp(tokens[1], \"{\")) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Invalid key statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* block_mask. */\n+\t*block_mask |= 1 << LEARNER_KEY_BLOCK;\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_key_block_parse(struct learner_spec *s,\n+\t\t\tuint32_t *block_mask,\n+\t\t\tchar **tokens,\n+\t\t\tuint32_t n_tokens,\n+\t\t\tuint32_t n_lines,\n+\t\t\tuint32_t *err_line,\n+\t\t\tconst char **err_msg)\n+{\n+\tconst char **new_field_names = NULL;\n+\tchar *field_name = NULL;\n+\n+\t/* Handle end of block. */\n+\tif ((n_tokens == 1) && !strcmp(tokens[0], \"}\")) {\n+\t\t*block_mask &= ~(1 << LEARNER_KEY_BLOCK);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Check input arguments. */\n+\tif (n_tokens != 1) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Invalid match field statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tfield_name = strdup(tokens[0]);\n+\tnew_field_names = realloc(s->params.field_names, (s->params.n_fields + 1) * sizeof(char *));\n+\tif (!field_name || !new_field_names) {\n+\t\tfree(field_name);\n+\t\tfree(new_field_names);\n+\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Memory allocation failed.\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\ts->params.field_names = new_field_names;\n+\ts->params.field_names[s->params.n_fields] = field_name;\n+\ts->params.n_fields++;\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_actions_statement_parse(uint32_t *block_mask,\n+\t\t\t\tchar **tokens,\n+\t\t\t\tuint32_t n_tokens,\n+\t\t\t\tuint32_t n_lines,\n+\t\t\t\tuint32_t *err_line,\n+\t\t\t\tconst char **err_msg)\n+{\n+\t/* Check format. */\n+\tif ((n_tokens != 2) || strcmp(tokens[1], \"{\")) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Invalid actions statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* block_mask. */\n+\t*block_mask |= 1 << LEARNER_ACTIONS_BLOCK;\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_actions_block_parse(struct learner_spec *s,\n+\t\t\t    uint32_t *block_mask,\n+\t\t\t    char **tokens,\n+\t\t\t    uint32_t n_tokens,\n+\t\t\t    uint32_t n_lines,\n+\t\t\t    uint32_t *err_line,\n+\t\t\t    const char **err_msg)\n+{\n+\tconst char **new_action_names = NULL;\n+\tconst char **new_action_field_names = NULL;\n+\tchar *action_name = NULL, *action_field_name = NULL;\n+\tint has_args = 1;\n+\n+\t/* Handle end of block. */\n+\tif ((n_tokens == 1) && !strcmp(tokens[0], \"}\")) {\n+\t\t*block_mask &= ~(1 << LEARNER_ACTIONS_BLOCK);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Check input arguments. */\n+\tif ((n_tokens != 3) || strcmp(tokens[1], \"args\")) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Invalid action name statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!strcmp(tokens[2], \"none\"))\n+\t\thas_args = 0;\n+\n+\taction_name = strdup(tokens[0]);\n+\n+\tif (has_args)\n+\t\taction_field_name = strdup(tokens[2]);\n+\n+\tnew_action_names = realloc(s->params.action_names,\n+\t\t\t\t   (s->params.n_actions + 1) * sizeof(char *));\n+\n+\tnew_action_field_names = realloc(s->params.action_field_names,\n+\t\t\t\t\t (s->params.n_actions + 1) * sizeof(char *));\n+\n+\tif (!action_name ||\n+\t    (has_args && !action_field_name) ||\n+\t    !new_action_names ||\n+\t    !new_action_field_names) {\n+\t\tfree(action_name);\n+\t\tfree(action_field_name);\n+\t\tfree(new_action_names);\n+\t\tfree(new_action_field_names);\n+\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Memory allocation failed.\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\ts->params.action_names = new_action_names;\n+\ts->params.action_names[s->params.n_actions] = action_name;\n+\ts->params.action_field_names = new_action_field_names;\n+\ts->params.action_field_names[s->params.n_actions] = action_field_name;\n+\ts->params.n_actions++;\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_statement_parse(struct learner_spec *s,\n+\t\t      uint32_t *block_mask,\n+\t\t      char **tokens,\n+\t\t      uint32_t n_tokens,\n+\t\t      uint32_t n_lines,\n+\t\t      uint32_t *err_line,\n+\t\t      const char **err_msg)\n+{\n+\t/* Check format. */\n+\tif ((n_tokens != 3) || strcmp(tokens[2], \"{\")) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Invalid learner statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* spec. */\n+\ts->name = strdup(tokens[1]);\n+\tif (!s->name) {\n+\t\tif (err_line)\n+\t\t\t*err_line = n_lines;\n+\t\tif (err_msg)\n+\t\t\t*err_msg = \"Memory allocation failed.\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\t/* block_mask. */\n+\t*block_mask |= 1 << LEARNER_BLOCK;\n+\n+\treturn 0;\n+}\n+\n+static int\n+learner_block_parse(struct learner_spec *s,\n+\t\t    uint32_t *block_mask,\n+\t\t    char **tokens,\n+\t\t    uint32_t n_tokens,\n+\t\t    uint32_t n_lines,\n+\t\t    uint32_t *err_line,\n+\t\t    const char **err_msg)\n+{\n+\tif (*block_mask & (1 << LEARNER_KEY_BLOCK))\n+\t\treturn learner_key_block_parse(s,\n+\t\t\t\t\t       block_mask,\n+\t\t\t\t\t       tokens,\n+\t\t\t\t\t       n_tokens,\n+\t\t\t\t\t       n_lines,\n+\t\t\t\t\t       err_line,\n+\t\t\t\t\t       err_msg);\n+\n+\tif (*block_mask & (1 << LEARNER_ACTIONS_BLOCK))\n+\t\treturn learner_actions_block_parse(s,\n+\t\t\t\t\t\t   block_mask,\n+\t\t\t\t\t\t   tokens,\n+\t\t\t\t\t\t   n_tokens,\n+\t\t\t\t\t\t   n_lines,\n+\t\t\t\t\t\t   err_line,\n+\t\t\t\t\t\t   err_msg);\n+\n+\t/* Handle end of block. */\n+\tif ((n_tokens == 1) && !strcmp(tokens[0], \"}\")) {\n+\t\t*block_mask &= ~(1 << LEARNER_BLOCK);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"key\"))\n+\t\treturn learner_key_statement_parse(block_mask,\n+\t\t\t\t\t\t   tokens,\n+\t\t\t\t\t\t   n_tokens,\n+\t\t\t\t\t\t   n_lines,\n+\t\t\t\t\t\t   err_line,\n+\t\t\t\t\t\t   err_msg);\n+\n+\tif (!strcmp(tokens[0], \"actions\"))\n+\t\treturn learner_actions_statement_parse(block_mask,\n+\t\t\t\t\t\t       tokens,\n+\t\t\t\t\t\t       n_tokens,\n+\t\t\t\t\t\t       n_lines,\n+\t\t\t\t\t\t       err_line,\n+\t\t\t\t\t\t       err_msg);\n+\n+\tif (!strcmp(tokens[0], \"default_action\")) {\n+\t\tif (((n_tokens != 4) && (n_tokens != 5)) ||\n+\t\t    strcmp(tokens[2], \"args\") ||\n+\t\t    strcmp(tokens[3], \"none\") ||\n+\t\t    ((n_tokens == 5) && strcmp(tokens[4], \"const\"))) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Invalid default_action statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tif (s->params.default_action_name) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Duplicate default_action stmt.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->params.default_action_name = strdup(tokens[1]);\n+\t\tif (!s->params.default_action_name) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Memory allocation failed.\";\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\n+\t\tif (n_tokens == 5)\n+\t\t\ts->params.default_action_is_const = 1;\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"size\")) {\n+\t\tchar *p = tokens[1];\n+\n+\t\tif (n_tokens != 2) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Invalid size statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->size = strtoul(p, &p, 0);\n+\t\tif (p[0]) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Invalid size argument.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"timeout\")) {\n+\t\tchar *p = tokens[1];\n+\n+\t\tif (n_tokens != 2) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Invalid timeout statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->timeout = strtoul(p, &p, 0);\n+\t\tif (p[0]) {\n+\t\t\tif (err_line)\n+\t\t\t\t*err_line = n_lines;\n+\t\t\tif (err_msg)\n+\t\t\t\t*err_msg = \"Invalid timeout argument.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Anything else. */\n+\tif (err_line)\n+\t\t*err_line = n_lines;\n+\tif (err_msg)\n+\t\t*err_msg = \"Invalid statement.\";\n+\treturn -EINVAL;\n+}\n+\n /*\n  * regarray.\n  *\n@@ -1545,6 +1962,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \tstruct action_spec action_spec = {0};\n \tstruct table_spec table_spec = {0};\n \tstruct selector_spec selector_spec = {0};\n+\tstruct learner_spec learner_spec = {0};\n \tstruct regarray_spec regarray_spec = {0};\n \tstruct metarray_spec metarray_spec = {0};\n \tstruct apply_spec apply_spec = {0};\n@@ -1761,6 +2179,40 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/* learner block. */\n+\t\tif (block_mask & (1 << LEARNER_BLOCK)) {\n+\t\t\tstatus = learner_block_parse(&learner_spec,\n+\t\t\t\t\t\t     &block_mask,\n+\t\t\t\t\t\t     tokens,\n+\t\t\t\t\t\t     n_tokens,\n+\t\t\t\t\t\t     n_lines,\n+\t\t\t\t\t\t     err_line,\n+\t\t\t\t\t\t     err_msg);\n+\t\t\tif (status)\n+\t\t\t\tgoto error;\n+\n+\t\t\tif (block_mask & (1 << LEARNER_BLOCK))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* End of block. */\n+\t\t\tstatus = rte_swx_pipeline_learner_config(p,\n+\t\t\t\tlearner_spec.name,\n+\t\t\t\t&learner_spec.params,\n+\t\t\t\tlearner_spec.size,\n+\t\t\t\tlearner_spec.timeout);\n+\t\t\tif (status) {\n+\t\t\t\tif (err_line)\n+\t\t\t\t\t*err_line = n_lines;\n+\t\t\t\tif (err_msg)\n+\t\t\t\t\t*err_msg = \"Learner table configuration error.\";\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n+\t\t\tlearner_spec_free(&learner_spec);\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\t/* apply block. */\n \t\tif (block_mask & (1 << APPLY_BLOCK)) {\n \t\t\tstatus = apply_block_parse(&apply_spec,\n@@ -1934,6 +2386,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/* learner. */\n+\t\tif (!strcmp(tokens[0], \"learner\")) {\n+\t\t\tstatus = learner_statement_parse(&learner_spec,\n+\t\t\t\t\t\t\t &block_mask,\n+\t\t\t\t\t\t\t tokens,\n+\t\t\t\t\t\t\t n_tokens,\n+\t\t\t\t\t\t\t n_lines,\n+\t\t\t\t\t\t\t err_line,\n+\t\t\t\t\t\t\t err_msg);\n+\t\t\tif (status)\n+\t\t\t\tgoto error;\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\t/* regarray. */\n \t\tif (!strcmp(tokens[0], \"regarray\")) {\n \t\t\tstatus = regarray_statement_parse(&regarray_spec,\n@@ -2042,6 +2509,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \taction_spec_free(&action_spec);\n \ttable_spec_free(&table_spec);\n \tselector_spec_free(&selector_spec);\n+\tlearner_spec_free(&learner_spec);\n \tregarray_spec_free(&regarray_spec);\n \tmetarray_spec_free(&metarray_spec);\n \tapply_spec_free(&apply_spec);\ndiff --git a/lib/pipeline/version.map b/lib/pipeline/version.map\nindex ff0974c2ee..c92d7d11cb 100644\n--- a/lib/pipeline/version.map\n+++ b/lib/pipeline/version.map\n@@ -129,4 +129,12 @@ EXPERIMENTAL {\n \trte_swx_ctl_selector_field_info_get;\n \trte_swx_ctl_selector_group_id_field_info_get;\n \trte_swx_ctl_selector_member_id_field_info_get;\n+\n+\t#added in 21.11\n+\trte_swx_ctl_pipeline_learner_default_entry_add;\n+\trte_swx_ctl_pipeline_learner_stats_read;\n+\trte_swx_ctl_learner_action_info_get;\n+\trte_swx_ctl_learner_info_get;\n+\trte_swx_ctl_learner_match_field_info_get;\n+\trte_swx_pipeline_learner_config;\n };\n",
    "prefixes": [
        "V3",
        "2/4"
    ]
}