get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 95235,
    "url": "https://patches.dpdk.org/api/patches/95235/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20210702224006.66043-1-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": "<20210702224006.66043-1-cristian.dumitrescu@intel.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20210702224006.66043-1-cristian.dumitrescu@intel.com",
    "date": "2021-07-02T22:40:06",
    "name": "[V2,3/5] pipeline: add support for selector tables",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "deb5a8df97ab62864e8fe2ecafdda0efa20791ec",
    "submitter": {
        "id": 19,
        "url": "https://patches.dpdk.org/api/people/19/?format=api",
        "name": "Cristian Dumitrescu",
        "email": "cristian.dumitrescu@intel.com"
    },
    "delegate": null,
    "mbox": "https://patches.dpdk.org/project/dpdk/patch/20210702224006.66043-1-cristian.dumitrescu@intel.com/mbox/",
    "series": [
        {
            "id": 17606,
            "url": "https://patches.dpdk.org/api/series/17606/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=17606",
            "date": "2021-07-02T22:40:06",
            "name": null,
            "version": 2,
            "mbox": "https://patches.dpdk.org/series/17606/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/95235/comments/",
    "check": "fail",
    "checks": "https://patches.dpdk.org/api/patches/95235/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 61BEAA0C3F;\n\tSat,  3 Jul 2021 00:40:12 +0200 (CEST)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 4F42F40696;\n\tSat,  3 Jul 2021 00:40:12 +0200 (CEST)",
            "from mga18.intel.com (mga18.intel.com [134.134.136.126])\n by mails.dpdk.org (Postfix) with ESMTP id BB40C4013F\n for <dev@dpdk.org>; Sat,  3 Jul 2021 00:40:09 +0200 (CEST)",
            "from orsmga008.jf.intel.com ([10.7.209.65])\n by orsmga106.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 02 Jul 2021 15:40:08 -0700",
            "from silpixa00400573.ir.intel.com (HELO\n silpixa00400573.ger.corp.intel.com) ([10.237.223.107])\n by orsmga008.jf.intel.com with ESMTP; 02 Jul 2021 15:40:07 -0700"
        ],
        "X-IronPort-AV": [
            "E=McAfee;i=\"6200,9189,10033\"; a=\"196075869\"",
            "E=Sophos;i=\"5.83,320,1616482800\"; d=\"scan'208\";a=\"196075869\"",
            "E=Sophos;i=\"5.83,320,1616482800\"; d=\"scan'208\";a=\"456057881\""
        ],
        "X-ExtLoop1": "1",
        "From": "Cristian Dumitrescu <cristian.dumitrescu@intel.com>",
        "To": "dev@dpdk.org",
        "Date": "Fri,  2 Jul 2021 23:40:06 +0100",
        "Message-Id": "<20210702224006.66043-1-cristian.dumitrescu@intel.com>",
        "X-Mailer": "git-send-email 2.17.1",
        "In-Reply-To": "<20210702204952.61445-1-cristian.dumitrescu@intel.com>",
        "References": "<20210702204952.61445-1-cristian.dumitrescu@intel.com>",
        "Subject": "[dpdk-dev] [PATCH V2 3/5] pipeline: add support for selector 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 selector tables,\n\nSigned-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>\n---\n lib/pipeline/rte_swx_ctl.c           | 700 ++++++++++++++++++++++++-\n lib/pipeline/rte_swx_ctl.h           | 253 +++++++++\n lib/pipeline/rte_swx_pipeline.c      | 748 ++++++++++++++++++++++++---\n lib/pipeline/rte_swx_pipeline.h      |  51 ++\n lib/pipeline/rte_swx_pipeline_spec.c | 354 ++++++++++++-\n lib/pipeline/version.map             |  13 +\n 6 files changed, 2034 insertions(+), 85 deletions(-)",
    "diff": "diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c\nindex 5d04e750f..8cabce2b9 100644\n--- a/lib/pipeline/rte_swx_ctl.c\n+++ b/lib/pipeline/rte_swx_ctl.c\n@@ -10,6 +10,8 @@\n #include <rte_common.h>\n #include <rte_byteorder.h>\n \n+#include <rte_swx_table_selector.h>\n+\n #include \"rte_swx_ctl.h\"\n \n #define CHECK(condition, err_code)                                             \\\n@@ -89,11 +91,44 @@ struct table {\n \tuint32_t n_delete;\n };\n \n+struct selector {\n+\t/* Selector table info. */\n+\tstruct rte_swx_ctl_selector_info info;\n+\n+\t/* group_id field. */\n+\tstruct rte_swx_ctl_table_match_field_info group_id_field;\n+\n+\t/* selector fields. */\n+\tstruct rte_swx_ctl_table_match_field_info *selector_fields;\n+\n+\t/* member_id field. */\n+\tstruct rte_swx_ctl_table_match_field_info member_id_field;\n+\n+\t/* Current selector table. Array of info.n_groups_max elements.*/\n+\tstruct rte_swx_table_selector_group **groups;\n+\n+\t/* Pending selector table subject to the next commit. Array of info.n_groups_max elements.\n+\t */\n+\tstruct rte_swx_table_selector_group **pending_groups;\n+\n+\t/* Valid flag per group. Array of n_groups_max elements. */\n+\tint *groups_added;\n+\n+\t/* Pending delete flag per group. Group deletion is subject to the next commit. Array of\n+\t * info.n_groups_max elements.\n+\t */\n+\tint *groups_pending_delete;\n+\n+\t/* Params. */\n+\tstruct rte_swx_table_selector_params params;\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 rte_swx_table_state *ts;\n \tstruct rte_swx_table_state *ts_next;\n \tint numa_node;\n@@ -709,6 +744,209 @@ table_free(struct rte_swx_ctl_pipeline *ctl)\n \tctl->tables = NULL;\n }\n \n+static void\n+selector_group_members_free(struct selector *s, uint32_t group_id)\n+{\n+\tstruct rte_swx_table_selector_group *group = s->groups[group_id];\n+\n+\tif (!group)\n+\t\treturn;\n+\n+\tfor ( ; ; ) {\n+\t\tstruct rte_swx_table_selector_member *m;\n+\n+\t\tm = TAILQ_FIRST(&group->members);\n+\t\tif (!m)\n+\t\t\tbreak;\n+\n+\t\tTAILQ_REMOVE(&group->members, m, node);\n+\t\tfree(m);\n+\t}\n+\n+\tfree(group);\n+\ts->groups[group_id] = NULL;\n+}\n+\n+static void\n+selector_pending_group_members_free(struct selector *s, uint32_t group_id)\n+{\n+\tstruct rte_swx_table_selector_group *group = s->pending_groups[group_id];\n+\n+\tif (!group)\n+\t\treturn;\n+\n+\tfor ( ; ; ) {\n+\t\tstruct rte_swx_table_selector_member *m;\n+\n+\t\tm = TAILQ_FIRST(&group->members);\n+\t\tif (!m)\n+\t\t\tbreak;\n+\n+\t\tTAILQ_REMOVE(&group->members, m, node);\n+\t\tfree(m);\n+\t}\n+\n+\tfree(group);\n+\ts->pending_groups[group_id] = NULL;\n+}\n+\n+static int\n+selector_group_duplicate_to_pending(struct selector *s, uint32_t group_id)\n+{\n+\tstruct rte_swx_table_selector_group *g, *gp;\n+\tstruct rte_swx_table_selector_member *m;\n+\n+\tselector_pending_group_members_free(s, group_id);\n+\n+\tg = s->groups[group_id];\n+\tgp = s->pending_groups[group_id];\n+\n+\tif (!gp) {\n+\t\tgp = calloc(1, sizeof(struct rte_swx_table_selector_group));\n+\t\tif (!gp)\n+\t\t\tgoto error;\n+\n+\t\tTAILQ_INIT(&gp->members);\n+\n+\t\ts->pending_groups[group_id] = gp;\n+\t}\n+\n+\tif (!g)\n+\t\treturn 0;\n+\n+\tTAILQ_FOREACH(m, &g->members, node) {\n+\t\tstruct rte_swx_table_selector_member *mp;\n+\n+\t\tmp = calloc(1, sizeof(struct rte_swx_table_selector_member));\n+\t\tif (!mp)\n+\t\t\tgoto error;\n+\n+\t\tmemcpy(mp, m, sizeof(struct rte_swx_table_selector_member));\n+\n+\t\tTAILQ_INSERT_TAIL(&gp->members, mp, node);\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tselector_pending_group_members_free(s, group_id);\n+\treturn -ENOMEM;\n+}\n+\n+static void\n+selector_free(struct rte_swx_ctl_pipeline *ctl)\n+{\n+\tuint32_t i;\n+\n+\tif (ctl->selectors)\n+\t\treturn;\n+\n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstruct selector *s = &ctl->selectors[i];\n+\t\tuint32_t i;\n+\n+\t\t/* selector_fields. */\n+\t\tfree(s->selector_fields);\n+\n+\t\t/* groups. */\n+\t\tif (s->groups)\n+\t\t\tfor (i = 0; i < s->info.n_groups_max; i++)\n+\t\t\t\tselector_group_members_free(s, i);\n+\n+\t\tfree(s->groups);\n+\n+\t\t/* pending_groups. */\n+\t\tif (s->pending_groups)\n+\t\t\tfor (i = 0; i < s->info.n_groups_max; i++)\n+\t\t\t\tselector_pending_group_members_free(s, i);\n+\n+\t\tfree(s->pending_groups);\n+\n+\t\t/* groups_added. */\n+\t\tfree(s->groups_added);\n+\n+\t\t/* groups_pending_delete. */\n+\t\tfree(s->groups_pending_delete);\n+\n+\t\t/* params. */\n+\t\tfree(s->params.selector_mask);\n+\t}\n+\n+\tfree(ctl->selectors);\n+\tctl->selectors = NULL;\n+}\n+\n+static struct selector *\n+selector_find(struct rte_swx_ctl_pipeline *ctl, const char *selector_name)\n+{\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstruct selector *s = &ctl->selectors[i];\n+\n+\t\tif (!strcmp(selector_name, s->info.name))\n+\t\t\treturn s;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int\n+selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n+{\n+\tstruct selector *s = &ctl->selectors[selector_id];\n+\tstruct rte_swx_ctl_table_match_field_info *first = NULL, *last = NULL;\n+\tuint8_t *selector_mask = NULL;\n+\tuint32_t selector_size = 0, selector_offset = 0, i;\n+\n+\t/* Find first (smallest offset) and last (biggest offset) match fields. */\n+\tfirst = &s->selector_fields[0];\n+\tlast = &s->selector_fields[0];\n+\n+\tfor (i = 1; i < s->info.n_selector_fields; i++) {\n+\t\tstruct rte_swx_ctl_table_match_field_info *f = &s->selector_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/* selector_offset. */\n+\tselector_offset = first->offset / 8;\n+\n+\t/* selector_size. */\n+\tselector_size = (last->offset + last->n_bits - first->offset) / 8;\n+\n+\t/* selector_mask. */\n+\tselector_mask = calloc(1, selector_size);\n+\tif (!selector_mask)\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0; i < s->info.n_selector_fields; i++) {\n+\t\tstruct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];\n+\t\tuint32_t start;\n+\t\tsize_t size;\n+\n+\t\tstart = (f->offset - first->offset) / 8;\n+\t\tsize = f->n_bits / 8;\n+\n+\t\tmemset(&selector_mask[start], 0xFF, size);\n+\t}\n+\n+\t/* Fill in. */\n+\ts->params.group_id_offset = s->group_id_field.offset / 8;\n+\ts->params.selector_size = selector_size;\n+\ts->params.selector_offset = selector_offset;\n+\ts->params.selector_mask = selector_mask;\n+\ts->params.member_id_offset = s->member_id_field.offset / 8;\n+\ts->params.n_groups_max = s->info.n_groups_max;\n+\ts->params.n_members_per_group_max = s->info.n_members_per_group_max;\n+\n+\treturn 0;\n+}\n+\n static void\n table_state_free(struct rte_swx_ctl_pipeline *ctl)\n {\n@@ -730,6 +968,15 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)\n \t\t\ttable->ops.free(ts->obj);\n \t}\n \n+\t/* For each selector table, free its table state. */\n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstruct rte_swx_table_state *ts = &ctl->ts_next[i];\n+\n+\t\t/* Table object. */\n+\t\tif (ts->obj)\n+\t\t\trte_swx_table_selector_free(ts->obj);\n+\t}\n+\n \tfree(ctl->ts_next);\n \tctl->ts_next = NULL;\n }\n@@ -740,13 +987,14 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)\n \tint status = 0;\n \tuint32_t i;\n \n-\tctl->ts_next = calloc(ctl->info.n_tables,\n+\tctl->ts_next = calloc(ctl->info.n_tables + ctl->info.n_selectors,\n \t\t\t      sizeof(struct rte_swx_table_state));\n \tif (!ctl->ts_next) {\n \t\tstatus = -ENOMEM;\n \t\tgoto error;\n \t}\n \n+\t/* Tables. */\n \tfor (i = 0; i < ctl->info.n_tables; i++) {\n \t\tstruct table *table = &ctl->tables[i];\n \t\tstruct rte_swx_table_state *ts = &ctl->ts[i];\n@@ -782,6 +1030,19 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)\n \t\tts_next->default_action_id = ts->default_action_id;\n \t}\n \n+\t/* Selector tables. */\n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstruct selector *s = &ctl->selectors[i];\n+\t\tstruct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + i];\n+\n+\t\t/* Table object. */\n+\t\tts_next->obj = rte_swx_table_selector_create(&s->params, NULL, ctl->numa_node);\n+\t\tif (!ts_next->obj) {\n+\t\t\tstatus = -ENODEV;\n+\t\t\tgoto error;\n+\t\t}\n+\t}\n+\n \treturn 0;\n \n error:\n@@ -799,6 +1060,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)\n \n \ttable_state_free(ctl);\n \n+\tselector_free(ctl);\n+\n \ttable_free(ctl);\n \n \tfree(ctl);\n@@ -940,6 +1203,77 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)\n \t\t\tgoto error;\n \t}\n \n+\t/* selector tables. */\n+\tctl->selectors = calloc(ctl->info.n_selectors, sizeof(struct selector));\n+\tif (!ctl->selectors)\n+\t\tgoto error;\n+\n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstruct selector *s = &ctl->selectors[i];\n+\t\tuint32_t j;\n+\n+\t\t/* info. */\n+\t\tstatus = rte_swx_ctl_selector_info_get(p, i, &s->info);\n+\t\tif (status)\n+\t\t\tgoto error;\n+\n+\t\t/* group_id field. */\n+\t\tstatus = rte_swx_ctl_selector_group_id_field_info_get(p,\n+\t\t\ti,\n+\t\t\t&s->group_id_field);\n+\t\tif (status)\n+\t\t\tgoto error;\n+\n+\t\t/* selector fields. */\n+\t\ts->selector_fields = calloc(s->info.n_selector_fields,\n+\t\t\tsizeof(struct rte_swx_ctl_table_match_field_info));\n+\t\tif (!s->selector_fields)\n+\t\t\tgoto error;\n+\n+\t\tfor (j = 0; j < s->info.n_selector_fields; j++) {\n+\t\t\tstatus = rte_swx_ctl_selector_field_info_get(p,\n+\t\t\t\ti,\n+\t\t\t\tj,\n+\t\t\t\t&s->selector_fields[j]);\n+\t\t\tif (status)\n+\t\t\t\tgoto error;\n+\t\t}\n+\n+\t\t/* member_id field. */\n+\t\tstatus = rte_swx_ctl_selector_member_id_field_info_get(p,\n+\t\t\ti,\n+\t\t\t&s->member_id_field);\n+\t\tif (status)\n+\t\t\tgoto error;\n+\n+\t\t/* groups. */\n+\t\ts->groups = calloc(s->info.n_groups_max,\n+\t\t\tsizeof(struct rte_swx_table_selector_group *));\n+\t\tif (!s->groups)\n+\t\t\tgoto error;\n+\n+\t\t/* pending_groups. */\n+\t\ts->pending_groups = calloc(s->info.n_groups_max,\n+\t\t\tsizeof(struct rte_swx_table_selector_group *));\n+\t\tif (!s->pending_groups)\n+\t\t\tgoto error;\n+\n+\t\t/* groups_added. */\n+\t\ts->groups_added = calloc(s->info.n_groups_max, sizeof(int));\n+\t\tif (!s->groups_added)\n+\t\t\tgoto error;\n+\n+\t\t/* groups_pending_delete. */\n+\t\ts->groups_pending_delete = calloc(s->info.n_groups_max, sizeof(int));\n+\t\tif (!s->groups_pending_delete)\n+\t\t\tgoto error;\n+\n+\t\t/* params. */\n+\t\tstatus = selector_params_get(ctl, i);\n+\t\tif (status)\n+\t\t\tgoto error;\n+\t}\n+\n \t/* ts. */\n \tstatus = rte_swx_pipeline_table_state_get(p, &ctl->ts);\n \tif (status)\n@@ -1499,6 +1833,295 @@ table_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)\n \ttable_pending_default_free(table);\n }\n \n+int\n+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\tconst char *selector_name,\n+\t\t\t\t\tuint32_t *group_id)\n+{\n+\tstruct selector *s;\n+\tuint32_t i;\n+\n+\t/* Check input arguments. */\n+\tif (!ctl || !selector_name || !selector_name[0] || !group_id)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(ctl, selector_name);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\t/* Find an unused group. */\n+\tfor (i = 0; i < s->info.n_groups_max; i++)\n+\t\tif (!s->groups_added[i]) {\n+\t\t\t*group_id = i;\n+\t\t\ts->groups_added[i] = 1;\n+\t\t\treturn 0;\n+\t\t}\n+\n+\treturn -ENOSPC;\n+}\n+\n+int\n+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t   const char *selector_name,\n+\t\t\t\t\t   uint32_t group_id)\n+{\n+\tstruct selector *s;\n+\tstruct rte_swx_table_selector_group *group;\n+\n+\t/* Check input arguments. */\n+\tif (!ctl || !selector_name || !selector_name[0])\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(ctl, selector_name);\n+\tif (!s ||\n+\t   (group_id >= s->info.n_groups_max) ||\n+\t   !s->groups_added[group_id])\n+\t\treturn -EINVAL;\n+\n+\t/* Check if this group is already scheduled for deletion. */\n+\tif (s->groups_pending_delete[group_id])\n+\t\treturn 0;\n+\n+\t/* Initialize the pending group, if needed. */\n+\tif (!s->pending_groups[group_id]) {\n+\t\tint status;\n+\n+\t\tstatus = selector_group_duplicate_to_pending(s, group_id);\n+\t\tif (status)\n+\t\t\treturn status;\n+\t}\n+\n+\tgroup = s->pending_groups[group_id];\n+\n+\t/* Schedule removal of all the members from the current group. */\n+\tfor ( ; ; ) {\n+\t\tstruct rte_swx_table_selector_member *m;\n+\n+\t\tm = TAILQ_FIRST(&group->members);\n+\t\tif (!m)\n+\t\t\tbreak;\n+\n+\t\tTAILQ_REMOVE(&group->members, m, node);\n+\t\tfree(m);\n+\t}\n+\n+\t/* Schedule the group for deletion. */\n+\ts->groups_pending_delete[group_id] = 1;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t       const char *selector_name,\n+\t\t\t\t\t       uint32_t group_id,\n+\t\t\t\t\t       uint32_t member_id,\n+\t\t\t\t\t       uint32_t member_weight)\n+{\n+\tstruct selector *s;\n+\tstruct rte_swx_table_selector_group *group;\n+\tstruct rte_swx_table_selector_member *m;\n+\n+\tif (!member_weight)\n+\t\treturn rte_swx_ctl_pipeline_selector_group_member_delete(ctl,\n+\t\t\t\t\t\t\t\t\t selector_name,\n+\t\t\t\t\t\t\t\t\t group_id,\n+\t\t\t\t\t\t\t\t\t member_id);\n+\n+\t/* Check input arguments. */\n+\tif (!ctl || !selector_name || !selector_name[0])\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(ctl, selector_name);\n+\tif (!s ||\n+\t   (group_id >= s->info.n_groups_max) ||\n+\t   !s->groups_added[group_id] ||\n+\t   s->groups_pending_delete[group_id])\n+\t\treturn -EINVAL;\n+\n+\t/* Initialize the pending group, if needed. */\n+\tif (!s->pending_groups[group_id]) {\n+\t\tint status;\n+\n+\t\tstatus = selector_group_duplicate_to_pending(s, group_id);\n+\t\tif (status)\n+\t\t\treturn status;\n+\t}\n+\n+\tgroup = s->pending_groups[group_id];\n+\n+\t/* If this member is already in this group, then simply update its weight and return. */\n+\tTAILQ_FOREACH(m, &group->members, node)\n+\t\tif (m->member_id == member_id) {\n+\t\t\tm->member_weight = member_weight;\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t/* Add new member to this group. */\n+\tm = calloc(1, sizeof(struct rte_swx_table_selector_member));\n+\tif (!m)\n+\t\treturn -ENOMEM;\n+\n+\tm->member_id = member_id;\n+\tm->member_weight = member_weight;\n+\n+\tTAILQ_INSERT_TAIL(&group->members, m, node);\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t\t  const char *selector_name,\n+\t\t\t\t\t\t  uint32_t group_id __rte_unused,\n+\t\t\t\t\t\t  uint32_t member_id __rte_unused)\n+{\n+\tstruct selector *s;\n+\tstruct rte_swx_table_selector_group *group;\n+\tstruct rte_swx_table_selector_member *m;\n+\n+\t/* Check input arguments. */\n+\tif (!ctl || !selector_name || !selector_name[0])\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(ctl, selector_name);\n+\tif (!s ||\n+\t    (group_id >= s->info.n_groups_max) ||\n+\t    !s->groups_added[group_id] ||\n+\t    s->groups_pending_delete[group_id])\n+\t\treturn -EINVAL;\n+\n+\t/* Initialize the pending group, if needed. */\n+\tif (!s->pending_groups[group_id]) {\n+\t\tint status;\n+\n+\t\tstatus = selector_group_duplicate_to_pending(s, group_id);\n+\t\tif (status)\n+\t\t\treturn status;\n+\t}\n+\n+\tgroup = s->pending_groups[group_id];\n+\n+\t/* Look for this member in the group and remove it, if found. */\n+\tTAILQ_FOREACH(m, &group->members, node)\n+\t\tif (m->member_id == member_id) {\n+\t\t\tTAILQ_REMOVE(&group->members, m, node);\n+\t\t\tfree(m);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+selector_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n+{\n+\tstruct selector *s = &ctl->selectors[selector_id];\n+\tstruct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];\n+\tuint32_t group_id;\n+\n+\t/* Push pending group member changes (s->pending_groups[group_id]) to the selector table\n+\t * mirror copy (ts_next->obj).\n+\t */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++) {\n+\t\tstruct rte_swx_table_selector_group *group = s->pending_groups[group_id];\n+\t\tint status;\n+\n+\t\t/* Skip this group if no change needed. */\n+\t\tif (!group)\n+\t\t\tcontinue;\n+\n+\t\t/* Apply the pending changes for the current group. */\n+\t\tstatus = rte_swx_table_selector_group_set(ts_next->obj, group_id, group);\n+\t\tif (status)\n+\t\t\treturn status;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+selector_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n+{\n+\tstruct selector *s = &ctl->selectors[selector_id];\n+\tuint32_t group_id;\n+\n+\t/* Commit pending group member changes (s->pending_groups[group_id]) to the stable group\n+\t * records (s->groups[group_id).\n+\t */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++) {\n+\t\tstruct rte_swx_table_selector_group *g = s->groups[group_id];\n+\t\tstruct rte_swx_table_selector_group *gp = s->pending_groups[group_id];\n+\n+\t\t/* Skip this group if no change needed. */\n+\t\tif (!gp)\n+\t\t\tcontinue;\n+\n+\t\t/* Transition the pending changes to stable. */\n+\t\ts->groups[group_id] = gp;\n+\t\ts->pending_groups[group_id] = NULL;\n+\n+\t\t/* Free the old group member list. */\n+\t\tif (!g)\n+\t\t\tcontinue;\n+\n+\t\tfor ( ; ; ) {\n+\t\t\tstruct rte_swx_table_selector_member *m;\n+\n+\t\t\tm = TAILQ_FIRST(&g->members);\n+\t\t\tif (!m)\n+\t\t\t\tbreak;\n+\n+\t\t\tTAILQ_REMOVE(&g->members, m, node);\n+\t\t\tfree(m);\n+\t\t}\n+\n+\t\tfree(g);\n+\t}\n+\n+\t/* Commit pending group validity changes (from s->groups_pending_delete[group_id] to\n+\t * s->groups_added[group_id].\n+\t */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++)\n+\t\tif (s->groups_pending_delete[group_id]) {\n+\t\t\ts->groups_added[group_id] = 0;\n+\t\t\ts->groups_pending_delete[group_id] = 0;\n+\t\t}\n+}\n+\n+static void\n+selector_rollback(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n+{\n+\tstruct selector *s = &ctl->selectors[selector_id];\n+\tstruct rte_swx_table_state *ts = &ctl->ts[ctl->info.n_tables + selector_id];\n+\tstruct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];\n+\tuint32_t group_id;\n+\n+\t/* Discard any previous changes to the selector table mirror copy (ts_next->obj). */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++) {\n+\t\tstruct rte_swx_table_selector_group *gp = s->pending_groups[group_id];\n+\n+\t\tif (gp) {\n+\t\t\tts_next->obj = ts->obj;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n+static void\n+selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)\n+{\n+\tstruct selector *s = &ctl->selectors[selector_id];\n+\tuint32_t group_id;\n+\n+\t/* Discard any pending group member changes (s->pending_groups[group_id]). */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++)\n+\t\tselector_pending_group_members_free(s, group_id);\n+\n+\t/* Discard any pending group deletions. */\n+\tmemset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));\n+}\n+\n int\n rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n {\n@@ -1508,8 +2131,8 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \n \tCHECK(ctl, EINVAL);\n \n-\t/* Operate the changes on the current ts_next before it becomes the new\n-\t * ts.\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 */\n \tfor (i = 0; i < ctl->info.n_tables; i++) {\n \t\tstatus = table_rollfwd0(ctl, i, 0);\n@@ -1517,6 +2140,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\t\tgoto rollback;\n \t}\n \n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tstatus = selector_rollfwd(ctl, i);\n+\t\tif (status)\n+\t\t\tgoto rollback;\n+\t}\n+\n \tfor (i = 0; i < ctl->info.n_tables; i++)\n \t\ttable_rollfwd1(ctl, i);\n \n@@ -1529,7 +2158,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \tctl->ts = ctl->ts_next;\n \tctl->ts_next = ts;\n \n-\t/* Operate the changes on the current ts_next, which is the previous ts.\n+\t/* Operate the changes on the current ts_next, which is the previous ts, in order to get\n+\t * the current ts_next in sync with the current ts. Since the changes that can fail did\n+\t * not fail on the previous ts_next, it is guaranteed that they will not fail on the\n+\t * current ts_next, hence no error checking is needed.\n \t */\n \tfor (i = 0; i < ctl->info.n_tables; i++) {\n \t\ttable_rollfwd0(ctl, i, 1);\n@@ -1537,6 +2169,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\ttable_rollfwd2(ctl, i);\n \t}\n \n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tselector_rollfwd(ctl, i);\n+\t\tselector_rollfwd_finalize(ctl, i);\n+\t}\n+\n \treturn 0;\n \n rollback:\n@@ -1546,6 +2183,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)\n \t\t\ttable_abort(ctl, i);\n \t}\n \n+\tfor (i = 0; i < ctl->info.n_selectors; i++) {\n+\t\tselector_rollback(ctl, i);\n+\t\tif (abort_on_fail)\n+\t\t\tselector_abort(ctl, i);\n+\t}\n+\n \treturn status;\n }\n \n@@ -1559,6 +2202,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)\n \n \tfor (i = 0; i < ctl->info.n_tables; i++)\n \t\ttable_abort(ctl, i);\n+\n+\tfor (i = 0; i < ctl->info.n_selectors; i++)\n+\t\tselector_abort(ctl, i);\n }\n \n static int\n@@ -1858,3 +2504,49 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,\n \t\tn_entries);\n \treturn 0;\n }\n+\n+int\n+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,\n+\t\t\t\t      struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t      const char *selector_name)\n+{\n+\tstruct selector *s;\n+\tuint32_t group_id;\n+\n+\tif (!f || !ctl || !selector_name || !selector_name[0])\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(ctl, selector_name);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\t/* Selector. */\n+\tfprintf(f, \"# Selector %s: max groups %u, max members per group %u\\n\",\n+\t\ts->info.name,\n+\t\ts->info.n_groups_max,\n+\t\ts->info.n_members_per_group_max);\n+\n+\t/* Groups. */\n+\tfor (group_id = 0; group_id < s->info.n_groups_max; group_id++) {\n+\t\tstruct rte_swx_table_selector_group *group = s->groups[group_id];\n+\t\tstruct rte_swx_table_selector_member *m;\n+\t\tuint32_t n_members = 0;\n+\n+\t\tfprintf(f, \"Group %u = [\", group_id);\n+\n+\t\t/* Non-empty group. */\n+\t\tif (group)\n+\t\t\tTAILQ_FOREACH(m, &group->members, node) {\n+\t\t\t\tfprintf(f, \"%u:%u \", m->member_id, m->member_weight);\n+\t\t\t\tn_members++;\n+\t\t\t}\n+\n+\t\t/* Empty group. */\n+\t\tif (!n_members)\n+\t\t\tfprintf(f, \"0:1 \");\n+\n+\t\tfprintf(f, \"]\\n\");\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h\nindex dee788be8..f37301cf9 100644\n--- a/lib/pipeline/rte_swx_ctl.h\n+++ b/lib/pipeline/rte_swx_ctl.h\n@@ -22,6 +22,7 @@ extern \"C\" {\n \n #include \"rte_swx_port.h\"\n #include \"rte_swx_table.h\"\n+#include \"rte_swx_table_selector.h\"\n \n struct rte_swx_pipeline;\n \n@@ -48,6 +49,9 @@ struct rte_swx_ctl_pipeline_info {\n \t/** Number of tables. */\n \tuint32_t n_tables;\n \n+\t/** Number of selector tables. */\n+\tuint32_t n_selectors;\n+\n \t/** Number of register arrays. */\n \tuint32_t n_regarrays;\n \n@@ -385,6 +389,129 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,\n \t\t\t\t      const char *table_name,\n \t\t\t\t      struct rte_swx_table_stats *stats);\n \n+/*\n+ * Selector Table Query API.\n+ */\n+\n+/** Selector info. */\n+struct rte_swx_ctl_selector_info {\n+\t/** Selector table name. */\n+\tchar name[RTE_SWX_CTL_NAME_SIZE];\n+\n+\t/** Number of selector fields. */\n+\tuint32_t n_selector_fields;\n+\n+\t/** Maximum number of groups. */\n+\tuint32_t n_groups_max;\n+\n+\t/** Maximum number of members per group. */\n+\tuint32_t n_members_per_group_max;\n+};\n+\n+/**\n+ * Selector table info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] selector_id\n+ *   Selector table ID (0 .. *n_selectors* - 1).\n+ * @param[out] selector\n+ *   Selector 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_selector_info_get(struct rte_swx_pipeline *p,\n+\t\t\t      uint32_t selector_id,\n+\t\t\t      struct rte_swx_ctl_selector_info *selector);\n+\n+/**\n+ * Selector table \"group ID\" field info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] selector_id\n+ *   Selector table ID (0 .. *n_selectors*).\n+ * @param[out] field\n+ *   Selector table \"group ID\" 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_selector_group_id_field_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t\t     uint32_t selector_id,\n+\t\t\t\t\t     struct rte_swx_ctl_table_match_field_info *field);\n+\n+/**\n+ * Sselector table selector field info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] selector_id\n+ *   Selector table ID (0 .. *n_selectors*).\n+ * @param[in] selector_field_id\n+ *   Selector table selector field ID (0 .. *n_selector_fields* - 1).\n+ * @param[out] field\n+ *   Selector table selector 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_selector_field_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t    uint32_t selector_id,\n+\t\t\t\t    uint32_t selector_field_id,\n+\t\t\t\t    struct rte_swx_ctl_table_match_field_info *field);\n+\n+/**\n+ * Selector table \"member ID\" field info get\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] selector_id\n+ *   Selector table ID (0 .. *n_selectors*).\n+ * @param[out] field\n+ *   Selector table \"member ID\" 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_selector_member_id_field_info_get(struct rte_swx_pipeline *p,\n+\t\t\t\t\t      uint32_t selector_id,\n+\t\t\t\t\t      struct rte_swx_ctl_table_match_field_info *field);\n+\n+/** Selector table statistics. */\n+struct rte_swx_pipeline_selector_stats {\n+\t/** Number of packets. */\n+\tuint64_t n_pkts;\n+};\n+\n+/**\n+ * Selector table statistics counters read\n+ *\n+ * @param[in] p\n+ *   Pipeline handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\n+ * @param[out] stats\n+ *   Selector table stats. Must point to a pre-allocated structure.\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_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  * Table Update API.\n  */\n@@ -529,6 +656,111 @@ rte_swx_ctl_pipeline_table_entry_delete(struct rte_swx_ctl_pipeline *ctl,\n \t\t\t\t\tconst char *table_name,\n \t\t\t\t\tstruct rte_swx_table_entry *entry);\n \n+/**\n+ * Pipeline selector table group add\n+ *\n+ * Add a new group to a selector table. This operation is executed before this\n+ * function returns and its result is independent of the result of the next\n+ * commit operation.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\n+ * @param[out] group_id\n+ *   The ID of the new group. Only valid when the function call is successful.\n+ *   This group is initially empty, i.e. it does not contain any members.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument;\n+ *   -ENOSPC: All groups are currently in use, no group available.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\tconst char *selector_name,\n+\t\t\t\t\tuint32_t *group_id);\n+\n+/**\n+ * Pipeline selector table group delete\n+ *\n+ * Schedule a group for deletion as part of the next commit operation. The group\n+ * to be deleted can be empty or non-empty.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\n+ * @param[in] group_id\n+ *   Group to be deleted from the selector table.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument;\n+ *   -ENOMEM: Not enough memory.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t   const char *selector_name,\n+\t\t\t\t\t   uint32_t group_id);\n+\n+/**\n+ * Pipeline selector table member add to group\n+ *\n+ * Schedule the operation to add a new member to an existing group as part of\n+ * the next commit operation. If this member is already in this group, the\n+ * member weight is updated to the new value. A weight of zero means this member\n+ * is to be deleted from the group.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\n+ * @param[in] group_id\n+ *   The group ID.\n+ * @param[in] member_id\n+ *   The member to be added to the group.\n+ * @param[in] member_weight\n+ *   Member weight.\n+ * @return\n+ *   0 on success or the following error codes otherwise:\n+ *   -EINVAL: Invalid argument;\n+ *   -ENOMEM: Not enough memory;\n+ *   -ENOSPC: The group is full.\n+ */\n+__rte_experimental\n+int\n+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t       const char *selector_name,\n+\t\t\t\t\t       uint32_t group_id,\n+\t\t\t\t\t       uint32_t member_id,\n+\t\t\t\t\t       uint32_t member_weight);\n+\n+/**\n+ * Pipeline selector table member delete from group\n+ *\n+ * Schedule the operation to delete a member from an existing group as part of\n+ * the next commit operation.\n+ *\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\n+ * @param[in] group_id\n+ *   The group ID. Must be valid.\n+ * @param[in] member_id\n+ *   The member to be added to the group. Must be valid.\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_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t\t\t  const char *selector_name,\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 commit\n  *\n@@ -608,6 +840,27 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,\n \t\t\t\t   struct rte_swx_ctl_pipeline *ctl,\n \t\t\t\t   const char *table_name);\n \n+/**\n+ * Pipeline selector print to file\n+ *\n+ * Print all the selector entries to file.\n+ *\n+ * @param[in] f\n+ *   Output file.\n+ * @param[in] ctl\n+ *   Pipeline control handle.\n+ * @param[in] selector_name\n+ *   Selector table name.\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_selector_fprintf(FILE *f,\n+\t\t\t\t      struct rte_swx_ctl_pipeline *ctl,\n+\t\t\t\t      const char *selector_name);\n+\n /*\n  * Register Array Query API.\n  */\ndiff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c\nindex a2732a1e5..22c860f28 100644\n--- a/lib/pipeline/rte_swx_pipeline.c\n+++ b/lib/pipeline/rte_swx_pipeline.c\n@@ -15,6 +15,8 @@\n #include <rte_cycles.h>\n #include <rte_meter.h>\n \n+#include <rte_swx_table_selector.h>\n+\n #include \"rte_swx_pipeline.h\"\n #include \"rte_swx_ctl.h\"\n \n@@ -498,6 +500,7 @@ enum instruction_type {\n \n \t/* table TABLE */\n \tINSTR_TABLE,\n+\tINSTR_SELECTOR,\n \n \t/* extern e.obj.func */\n \tINSTR_EXTERN_OBJ,\n@@ -794,6 +797,38 @@ struct table_statistics {\n \tuint64_t *n_pkts_action;\n };\n \n+/*\n+ * Selector.\n+ */\n+struct selector {\n+\tTAILQ_ENTRY(selector) node;\n+\tchar name[RTE_SWX_NAME_SIZE];\n+\n+\tstruct field *group_id_field;\n+\tstruct field **selector_fields;\n+\tuint32_t n_selector_fields;\n+\tstruct header *selector_header;\n+\tstruct field *member_id_field;\n+\n+\tuint32_t n_groups_max;\n+\tuint32_t n_members_per_group_max;\n+\n+\tuint32_t id;\n+};\n+\n+TAILQ_HEAD(selector_tailq, selector);\n+\n+struct selector_runtime {\n+\tvoid *mailbox;\n+\tuint8_t **group_id_buffer;\n+\tuint8_t **selector_buffer;\n+\tuint8_t **member_id_buffer;\n+};\n+\n+struct selector_statistics {\n+\tuint64_t n_pkts;\n+};\n+\n /*\n  * Register array.\n  */\n@@ -873,6 +908,7 @@ struct thread {\n \n \t/* Tables. */\n \tstruct table_runtime *tables;\n+\tstruct selector_runtime *selectors;\n \tstruct rte_swx_table_state *table_state;\n \tuint64_t action_id;\n \tint hit; /* 0 = Miss, 1 = Hit. */\n@@ -1308,6 +1344,7 @@ struct rte_swx_pipeline {\n \tstruct action_tailq actions;\n \tstruct table_type_tailq table_types;\n \tstruct table_tailq tables;\n+\tstruct selector_tailq selectors;\n \tstruct regarray_tailq regarrays;\n \tstruct meter_profile_tailq meter_profiles;\n \tstruct metarray_tailq metarrays;\n@@ -1317,6 +1354,7 @@ struct rte_swx_pipeline {\n \tstruct instruction **action_instructions;\n \tstruct rte_swx_table_state *table_state;\n \tstruct table_statistics *table_stats;\n+\tstruct selector_statistics *selector_stats;\n \tstruct regarray_runtime *regarray_runtime;\n \tstruct metarray_runtime *metarray_runtime;\n \tstruct instruction *instructions;\n@@ -1329,6 +1367,7 @@ struct rte_swx_pipeline {\n \tuint32_t n_extern_funcs;\n \tuint32_t n_actions;\n \tuint32_t n_tables;\n+\tuint32_t n_selectors;\n \tuint32_t n_regarrays;\n \tuint32_t n_metarrays;\n \tuint32_t n_headers;\n@@ -3450,6 +3489,9 @@ instr_hdr_invalidate_exec(struct rte_swx_pipeline *p)\n static struct table *\n table_find(struct rte_swx_pipeline *p, const char *name);\n \n+static struct selector *\n+selector_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@@ -3459,16 +3501,26 @@ instr_table_translate(struct rte_swx_pipeline *p,\n \t\t      struct instruction_data *data __rte_unused)\n {\n \tstruct table *t;\n+\tstruct selector *s;\n \n \tCHECK(!action, EINVAL);\n \tCHECK(n_tokens == 2, EINVAL);\n \n \tt = table_find(p, tokens[1]);\n-\tCHECK(t, EINVAL);\n+\tif (t) {\n+\t\tinstr->type = INSTR_TABLE;\n+\t\tinstr->table.table_id = t->id;\n+\t\treturn 0;\n+\t}\n \n-\tinstr->type = INSTR_TABLE;\n-\tinstr->table.table_id = t->id;\n-\treturn 0;\n+\ts = selector_find(p, tokens[1]);\n+\tif (s) {\n+\t\tinstr->type = INSTR_SELECTOR;\n+\t\tinstr->table.table_id = s->id;\n+\t\treturn 0;\n+\t}\n+\n+\tCHECK(0, EINVAL);\n }\n \n static inline void\n@@ -3522,6 +3574,45 @@ instr_table_exec(struct rte_swx_pipeline *p)\n \tthread_ip_action_call(p, t, action_id);\n }\n \n+static inline void\n+instr_selector_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 selector_id = ip->table.table_id;\n+\tstruct rte_swx_table_state *ts = &t->table_state[p->n_tables + selector_id];\n+\tstruct selector_runtime *selector = &t->selectors[selector_id];\n+\tstruct selector_statistics *stats = &p->selector_stats[selector_id];\n+\tuint64_t n_pkts = stats->n_pkts;\n+\tint done;\n+\n+\t/* Table. */\n+\tdone = rte_swx_table_selector_select(ts->obj,\n+\t\t\t   selector->mailbox,\n+\t\t\t   selector->group_id_buffer,\n+\t\t\t   selector->selector_buffer,\n+\t\t\t   selector->member_id_buffer);\n+\tif (!done) {\n+\t\t/* Thread. */\n+\t\tTRACE(\"[Thread %2u] selector %u (not finalized)\\n\",\n+\t\t      p->thread_id,\n+\t\t      selector_id);\n+\n+\t\tthread_yield(p);\n+\t\treturn;\n+\t}\n+\n+\n+\tTRACE(\"[Thread %2u] selector %u\\n\",\n+\t      p->thread_id,\n+\t      selector_id);\n+\n+\tstats->n_pkts = n_pkts + 1;\n+\n+\t/* Thread. */\n+\tthread_ip_inc(p);\n+}\n+\n /*\n  * extern.\n  */\n@@ -8787,6 +8878,7 @@ static instr_exec_t instruction_table[] = {\n \t[INSTR_METER_IMI] = instr_meter_imi_exec,\n \n \t[INSTR_TABLE] = instr_table_exec,\n+\t[INSTR_SELECTOR] = instr_selector_exec,\n \t[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,\n \t[INSTR_EXTERN_FUNC] = instr_extern_func_exec,\n \n@@ -9253,6 +9345,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,\n \n \tCHECK_NAME(name, EINVAL);\n \tCHECK(!table_find(p, name), EEXIST);\n+\tCHECK(!selector_find(p, name), EEXIST);\n \n \tCHECK(params, EINVAL);\n \n@@ -9455,82 +9548,6 @@ table_params_free(struct rte_swx_table_params *params)\n \tfree(params);\n }\n \n-static int\n-table_state_build(struct rte_swx_pipeline *p)\n-{\n-\tstruct table *table;\n-\n-\tp->table_state = calloc(p->n_tables,\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-\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-\n-\tfor (i = 0; i < p->n_tables; i++) {\n-\t\tstruct rte_swx_table_state *ts = &p->table_state[i];\n-\t\tstruct table *table = table_find_by_id(p, i);\n-\n-\t\t/* ts->obj. */\n-\t\tif (table->type && ts->obj)\n-\t\t\ttable->type->ops.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-\n-static void\n-table_state_free(struct rte_swx_pipeline *p)\n-{\n-\ttable_state_build_free(p);\n-}\n-\n static int\n table_stub_lkp(void *table __rte_unused,\n \t       void *mailbox __rte_unused,\n@@ -9658,6 +9675,458 @@ table_free(struct rte_swx_pipeline *p)\n \t}\n }\n \n+/*\n+ * Selector.\n+ */\n+static struct selector *\n+selector_find(struct rte_swx_pipeline *p, const char *name)\n+{\n+\tstruct selector *s;\n+\n+\tTAILQ_FOREACH(s, &p->selectors, node)\n+\t\tif (strcmp(s->name, name) == 0)\n+\t\t\treturn s;\n+\n+\treturn NULL;\n+}\n+\n+static struct selector *\n+selector_find_by_id(struct rte_swx_pipeline *p, uint32_t id)\n+{\n+\tstruct selector *s = NULL;\n+\n+\tTAILQ_FOREACH(s, &p->selectors, node)\n+\t\tif (s->id == id)\n+\t\t\treturn s;\n+\n+\treturn NULL;\n+}\n+\n+static int\n+selector_fields_check(struct rte_swx_pipeline *p,\n+\t\t      struct rte_swx_pipeline_selector_params *params,\n+\t\t      struct header **header)\n+{\n+\tstruct header *h0 = NULL;\n+\tstruct field *hf, *mf;\n+\tuint32_t i;\n+\n+\t/* Return if no selector fields. */\n+\tif (!params->n_selector_fields || !params->selector_field_names)\n+\t\treturn -EINVAL;\n+\n+\t/* Check that all the selector fields either belong to the same header\n+\t * or are all meta-data fields.\n+\t */\n+\thf = header_field_parse(p, params->selector_field_names[0], &h0);\n+\tmf = metadata_field_parse(p, params->selector_field_names[0]);\n+\tif (!hf && !mf)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 1; i < params->n_selector_fields; i++)\n+\t\tif (h0) {\n+\t\t\tstruct header *h;\n+\n+\t\t\thf = header_field_parse(p, params->selector_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->selector_field_names[i]);\n+\t\t\tif (!mf)\n+\t\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t/* Check that there are no duplicated match fields. */\n+\tfor (i = 0; i < params->n_selector_fields; i++) {\n+\t\tconst char *field_name = params->selector_field_names[i];\n+\t\tuint32_t j;\n+\n+\t\tfor (j = i + 1; j < params->n_selector_fields; j++)\n+\t\t\tif (!strcmp(params->selector_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+int\n+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+\tstruct selector *s;\n+\tstruct header *selector_header = NULL;\n+\tstruct field *group_id_field, *member_id_field;\n+\tuint32_t 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+\n+\tCHECK(params, EINVAL);\n+\n+\tCHECK_NAME(params->group_id_field_name, EINVAL);\n+\tgroup_id_field = metadata_field_parse(p, params->group_id_field_name);\n+\tCHECK(group_id_field, EINVAL);\n+\n+\tfor (i = 0; i < params->n_selector_fields; i++) {\n+\t\tconst char *field_name = params->selector_field_names[i];\n+\n+\t\tCHECK_NAME(field_name, EINVAL);\n+\t}\n+\tstatus = selector_fields_check(p, params, &selector_header);\n+\tif (status)\n+\t\treturn status;\n+\n+\tCHECK_NAME(params->member_id_field_name, EINVAL);\n+\tmember_id_field = metadata_field_parse(p, params->member_id_field_name);\n+\tCHECK(member_id_field, EINVAL);\n+\n+\tCHECK(params->n_groups_max, EINVAL);\n+\n+\tCHECK(params->n_members_per_group_max, EINVAL);\n+\n+\t/* Memory allocation. */\n+\ts = calloc(1, sizeof(struct selector));\n+\tif (!s) {\n+\t\tstatus = -ENOMEM;\n+\t\tgoto error;\n+\t}\n+\n+\ts->selector_fields = calloc(params->n_selector_fields, sizeof(struct field *));\n+\tif (!s->selector_fields) {\n+\t\tstatus = -ENOMEM;\n+\t\tgoto error;\n+\t}\n+\n+\t/* Node initialization. */\n+\tstrcpy(s->name, name);\n+\n+\ts->group_id_field = group_id_field;\n+\n+\tfor (i = 0; i < params->n_selector_fields; i++) {\n+\t\tconst char *field_name = params->selector_field_names[i];\n+\n+\t\ts->selector_fields[i] = selector_header ?\n+\t\t\theader_field_parse(p, field_name, NULL) :\n+\t\t\tmetadata_field_parse(p, field_name);\n+\t}\n+\n+\ts->n_selector_fields = params->n_selector_fields;\n+\n+\ts->selector_header = selector_header;\n+\n+\ts->member_id_field = member_id_field;\n+\n+\ts->n_groups_max = params->n_groups_max;\n+\n+\ts->n_members_per_group_max = params->n_members_per_group_max;\n+\n+\ts->id = p->n_selectors;\n+\n+\t/* Node add to tailq. */\n+\tTAILQ_INSERT_TAIL(&p->selectors, s, node);\n+\tp->n_selectors++;\n+\n+\treturn 0;\n+\n+error:\n+\tif (!s)\n+\t\treturn status;\n+\n+\tfree(s->selector_fields);\n+\n+\tfree(s);\n+\n+\treturn status;\n+}\n+\n+static void\n+selector_params_free(struct rte_swx_table_selector_params *params)\n+{\n+\tif (!params)\n+\t\treturn;\n+\n+\tfree(params->selector_mask);\n+\n+\tfree(params);\n+}\n+\n+static struct rte_swx_table_selector_params *\n+selector_table_params_get(struct selector *s)\n+{\n+\tstruct rte_swx_table_selector_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_pipeline_selector_params));\n+\tif (!params)\n+\t\tgoto error;\n+\n+\t/* Group ID. */\n+\tparams->group_id_offset = s->group_id_field->offset / 8;\n+\n+\t/* Find first (smallest offset) and last (biggest offset) selector fields. */\n+\tfirst = s->selector_fields[0];\n+\tlast = s->selector_fields[0];\n+\n+\tfor (i = 0; i < s->n_selector_fields; i++) {\n+\t\tstruct field *f = s->selector_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/* Selector offset and size. */\n+\tparams->selector_offset = first->offset / 8;\n+\tparams->selector_size = (last->offset + last->n_bits - first->offset) / 8;\n+\n+\t/* Memory allocation. */\n+\tparams->selector_mask = calloc(1, params->selector_size);\n+\tif (!params->selector_mask)\n+\t\tgoto error;\n+\n+\t/* Selector mask. */\n+\tfor (i = 0; i < s->n_selector_fields; i++) {\n+\t\tstruct field *f = s->selector_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->selector_mask[start], 0xFF, size);\n+\t}\n+\n+\t/* Member ID. */\n+\tparams->member_id_offset = s->member_id_field->offset / 8;\n+\n+\t/* Maximum number of groups. */\n+\tparams->n_groups_max = s->n_groups_max;\n+\n+\t/* Maximum number of members per group. */\n+\tparams->n_members_per_group_max = s->n_members_per_group_max;\n+\n+\treturn params;\n+\n+error:\n+\tselector_params_free(params);\n+\treturn NULL;\n+}\n+\n+static void\n+selector_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->selectors)\n+\t\t\tcontinue;\n+\n+\t\tfor (j = 0; j < p->n_selectors; j++) {\n+\t\t\tstruct selector_runtime *r = &t->selectors[j];\n+\n+\t\t\tfree(r->mailbox);\n+\t\t}\n+\n+\t\tfree(t->selectors);\n+\t\tt->selectors = NULL;\n+\t}\n+\n+\tfree(p->selector_stats);\n+\tp->selector_stats = NULL;\n+}\n+\n+static int\n+selector_build(struct rte_swx_pipeline *p)\n+{\n+\tuint32_t i;\n+\tint status = 0;\n+\n+\t/* Per pipeline: selector statistics. */\n+\tp->selector_stats = calloc(p->n_selectors, sizeof(struct selector_statistics));\n+\tif (!p->selector_stats) {\n+\t\tstatus = -ENOMEM;\n+\t\tgoto error;\n+\t}\n+\n+\t/* Per thread: selector run-time. */\n+\tfor (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {\n+\t\tstruct thread *t = &p->threads[i];\n+\t\tstruct selector *s;\n+\n+\t\tt->selectors = calloc(p->n_selectors, sizeof(struct selector_runtime));\n+\t\tif (!t->selectors) {\n+\t\t\tstatus = -ENOMEM;\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tTAILQ_FOREACH(s, &p->selectors, node) {\n+\t\t\tstruct selector_runtime *r = &t->selectors[s->id];\n+\t\t\tuint64_t size;\n+\n+\t\t\t/* r->mailbox. */\n+\t\t\tsize = rte_swx_table_selector_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->group_id_buffer. */\n+\t\t\tr->group_id_buffer = &t->structs[p->metadata_struct_id];\n+\n+\t\t\t/* r->selector_buffer. */\n+\t\t\tr->selector_buffer = s->selector_header ?\n+\t\t\t\t&t->structs[s->selector_header->struct_id] :\n+\t\t\t\t&t->structs[p->metadata_struct_id];\n+\n+\t\t\t/* r->member_id_buffer. */\n+\t\t\tr->member_id_buffer = &t->structs[p->metadata_struct_id];\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tselector_build_free(p);\n+\treturn status;\n+}\n+\n+static void\n+selector_free(struct rte_swx_pipeline *p)\n+{\n+\tselector_build_free(p);\n+\n+\t/* Selector tables. */\n+\tfor ( ; ; ) {\n+\t\tstruct selector *elem;\n+\n+\t\telem = TAILQ_FIRST(&p->selectors);\n+\t\tif (!elem)\n+\t\t\tbreak;\n+\n+\t\tTAILQ_REMOVE(&p->selectors, elem, node);\n+\t\tfree(elem->selector_fields);\n+\t\tfree(elem);\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+\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+\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+\n+\tfor (i = 0; i < p->n_tables; i++) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[i];\n+\t\tstruct table *table = table_find_by_id(p, i);\n+\n+\t\t/* ts->obj. */\n+\t\tif (table->type && ts->obj)\n+\t\t\ttable->type->ops.free(ts->obj);\n+\n+\t\t/* ts->default_action_data. */\n+\t\tfree(ts->default_action_data);\n+\t}\n+\n+\tfor (i = 0; i < p->n_selectors; i++) {\n+\t\tstruct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];\n+\n+\t\t/* ts->obj. */\n+\t\tif (ts->obj)\n+\t\t\trte_swx_table_selector_free(ts->obj);\n+\t}\n+\n+\tfree(p->table_state);\n+\tp->table_state = NULL;\n+}\n+\n+static void\n+table_state_free(struct rte_swx_pipeline *p)\n+{\n+\ttable_state_build_free(p);\n+}\n+\n /*\n  * Register array.\n  */\n@@ -9988,6 +10457,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)\n \tTAILQ_INIT(&pipeline->actions);\n \tTAILQ_INIT(&pipeline->table_types);\n \tTAILQ_INIT(&pipeline->tables);\n+\tTAILQ_INIT(&pipeline->selectors);\n \tTAILQ_INIT(&pipeline->regarrays);\n \tTAILQ_INIT(&pipeline->meter_profiles);\n \tTAILQ_INIT(&pipeline->metarrays);\n@@ -10010,6 +10480,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)\n \tmetarray_free(p);\n \tregarray_free(p);\n \ttable_state_free(p);\n+\tselector_free(p);\n \ttable_free(p);\n \taction_free(p);\n \tmetadata_free(p);\n@@ -10089,6 +10560,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)\n \tif (status)\n \t\tgoto error;\n \n+\tstatus = selector_build(p);\n+\tif (status)\n+\t\tgoto error;\n+\n \tstatus = table_state_build(p);\n \tif (status)\n \t\tgoto error;\n@@ -10108,6 +10583,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+\tselector_build_free(p);\n \ttable_build_free(p);\n \taction_build_free(p);\n \tmetadata_build_free(p);\n@@ -10167,6 +10643,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,\n \tpipeline->n_ports_out = p->n_ports_out;\n \tpipeline->n_actions = n_actions;\n \tpipeline->n_tables = n_tables;\n+\tpipeline->n_selectors = p->n_selectors;\n \tpipeline->n_regarrays = p->n_regarrays;\n \tpipeline->n_metarrays = p->n_metarrays;\n \n@@ -10320,6 +10797,98 @@ rte_swx_ctl_table_ops_get(struct rte_swx_pipeline *p,\n \treturn 0;\n }\n \n+int\n+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,\n+\t\t\t      uint32_t selector_id,\n+\t\t\t      struct rte_swx_ctl_selector_info *selector)\n+{\n+\tstruct selector *s = NULL;\n+\n+\tif (!p || !selector)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find_by_id(p, selector_id);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\tstrcpy(selector->name, s->name);\n+\n+\tselector->n_selector_fields = s->n_selector_fields;\n+\tselector->n_groups_max = s->n_groups_max;\n+\tselector->n_members_per_group_max = s->n_members_per_group_max;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,\n+\t uint32_t selector_id,\n+\t struct rte_swx_ctl_table_match_field_info *field)\n+{\n+\tstruct selector *s;\n+\n+\tif (!p || (selector_id >= p->n_selectors) || !field)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find_by_id(p, selector_id);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\tfield->match_type = RTE_SWX_TABLE_MATCH_EXACT;\n+\tfield->is_header = 0;\n+\tfield->n_bits = s->group_id_field->n_bits;\n+\tfield->offset = s->group_id_field->offset;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,\n+\t uint32_t selector_id,\n+\t uint32_t selector_field_id,\n+\t struct rte_swx_ctl_table_match_field_info *field)\n+{\n+\tstruct selector *s;\n+\tstruct field *f;\n+\n+\tif (!p || (selector_id >= p->n_selectors) || !field)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find_by_id(p, selector_id);\n+\tif (!s || (selector_field_id >= s->n_selector_fields))\n+\t\treturn -EINVAL;\n+\n+\tf = s->selector_fields[selector_field_id];\n+\tfield->match_type = RTE_SWX_TABLE_MATCH_EXACT;\n+\tfield->is_header = s->selector_header ? 1 : 0;\n+\tfield->n_bits = f->n_bits;\n+\tfield->offset = f->offset;\n+\n+\treturn 0;\n+}\n+\n+int\n+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,\n+\t uint32_t selector_id,\n+\t struct rte_swx_ctl_table_match_field_info *field)\n+{\n+\tstruct selector *s;\n+\n+\tif (!p || (selector_id >= p->n_selectors) || !field)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find_by_id(p, selector_id);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\tfield->match_type = RTE_SWX_TABLE_MATCH_EXACT;\n+\tfield->is_header = 0;\n+\tfield->n_bits = s->member_id_field->n_bits;\n+\tfield->offset = s->member_id_field->offset;\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@@ -10405,6 +10974,25 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,\n \treturn 0;\n }\n \n+int\n+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,\n+\tconst char *selector_name,\n+\tstruct rte_swx_pipeline_selector_stats *stats)\n+{\n+\tstruct selector *s;\n+\n+\tif (!p || !selector_name || !selector_name[0] || !stats)\n+\t\treturn -EINVAL;\n+\n+\ts = selector_find(p, selector_name);\n+\tif (!s)\n+\t\treturn -EINVAL;\n+\n+\tstats->n_pkts = p->selector_stats[s->id].n_pkts;\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 feeb10a5c..cd395ac39 100644\n--- a/lib/pipeline/rte_swx_pipeline.h\n+++ b/lib/pipeline/rte_swx_pipeline.h\n@@ -612,6 +612,57 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,\n \t\t\t      const char *args,\n \t\t\t      uint32_t size);\n \n+/** Pipeline selector table parameters. */\n+struct rte_swx_pipeline_selector_params {\n+\t/** The group ID field. Input into the selection operation.\n+\t * Restriction: This field must be a meta-data field.\n+\t */\n+\tconst char *group_id_field_name;\n+\n+\t/** The set of fields used to select (through a hashing scheme) the\n+\t * member within the current group. Inputs into the seletion operation.\n+\t * Restriction: All the selector fields must be part of the same struct,\n+\t * i.e. part of the same header or part of the meta-data structure.\n+\t */\n+\tconst char **selector_field_names;\n+\n+\t/** The number of selector fields. Must be non-zero. */\n+\tuint32_t n_selector_fields;\n+\n+\t/** The member ID field. Output from the selection operation.\n+\t * Restriction: This field must be a meta-data field.\n+\t */\n+\tconst char *member_id_field_name;\n+\n+\t/** Maximum number of groups. Must be non-zero. */\n+\tuint32_t n_groups_max;\n+\n+\t/** Maximum number of members per group. Must be non-zero. */\n+\tuint32_t n_members_per_group_max;\n+};\n+\n+/**\n+ * Pipeline selector table configure\n+ *\n+ * @param[out] p\n+ *   Pipeline handle.\n+ * @param[in] name\n+ *   Selector table name.\n+ * @param[in] params\n+ *   Selector table parameters.\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: Selector table with this name already exists;\n+ *   -ENODEV: Selector table creation error.\n+ */\n+__rte_experimental\n+int\n+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 /**\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 2e867d7bf..6980b0390 100644\n--- a/lib/pipeline/rte_swx_pipeline_spec.c\n+++ b/lib/pipeline/rte_swx_pipeline_spec.c\n@@ -18,7 +18,9 @@\n #define TABLE_BLOCK 2\n #define TABLE_KEY_BLOCK 3\n #define TABLE_ACTIONS_BLOCK 4\n-#define APPLY_BLOCK 5\n+#define SELECTOR_BLOCK 5\n+#define SELECTOR_SELECTOR_BLOCK 6\n+#define APPLY_BLOCK 7\n \n /*\n  * extobj.\n@@ -940,6 +942,307 @@ table_block_parse(struct table_spec *s,\n \treturn -EINVAL;\n }\n \n+/*\n+ * selector.\n+ *\n+ * selector SELECTOR_NAME {\n+ *\tgroup_id FIELD_NAME\n+ *\tselector {\n+ *\t\tFIELD_NAME\n+ *\t\t...\n+ *\t}\n+ *\tmember_id FIELD_NAME\n+ *\tn_groups N_GROUPS\n+ *\tn_members_per_group N_MEMBERS_PER_GROUP\n+ * }\n+ */\n+struct selector_spec {\n+\tchar *name;\n+\tstruct rte_swx_pipeline_selector_params params;\n+};\n+\n+static void\n+selector_spec_free(struct selector_spec *s)\n+{\n+\tuintptr_t field_name;\n+\tuint32_t i;\n+\n+\tif (!s)\n+\t\treturn;\n+\n+\t/* name. */\n+\tfree(s->name);\n+\ts->name = NULL;\n+\n+\t/* params->group_id_field_name. */\n+\tfield_name = (uintptr_t)s->params.group_id_field_name;\n+\tfree((void *)field_name);\n+\ts->params.group_id_field_name = NULL;\n+\n+\t/* params->selector_field_names. */\n+\tfor (i = 0; i < s->params.n_selector_fields; i++) {\n+\t\tfield_name = (uintptr_t)s->params.selector_field_names[i];\n+\n+\t\tfree((void *)field_name);\n+\t}\n+\n+\tfree(s->params.selector_field_names);\n+\ts->params.selector_field_names = NULL;\n+\n+\ts->params.n_selector_fields = 0;\n+\n+\t/* params->member_id_field_name. */\n+\tfield_name = (uintptr_t)s->params.member_id_field_name;\n+\tfree((void *)field_name);\n+\ts->params.member_id_field_name = NULL;\n+\n+\t/* params->n_groups_max. */\n+\ts->params.n_groups_max = 0;\n+\n+\t/* params->n_members_per_group_max. */\n+\ts->params.n_members_per_group_max = 0;\n+}\n+\n+static int\n+selector_statement_parse(struct selector_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+\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 selector 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 << SELECTOR_BLOCK;\n+\n+\treturn 0;\n+}\n+\n+static int\n+selector_selector_statement_parse(uint32_t *block_mask,\n+\t\t\t\t  char **tokens,\n+\t\t\t\t  uint32_t n_tokens,\n+\t\t\t\t  uint32_t n_lines,\n+\t\t\t\t  uint32_t *err_line,\n+\t\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 selector statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* block_mask. */\n+\t*block_mask |= 1 << SELECTOR_SELECTOR_BLOCK;\n+\n+\treturn 0;\n+}\n+\n+static int\n+selector_selector_block_parse(struct selector_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_fields;\n+\tchar *name;\n+\n+\t/* Handle end of block. */\n+\tif ((n_tokens == 1) && !strcmp(tokens[0], \"}\")) {\n+\t\t*block_mask &= ~(1 << SELECTOR_SELECTOR_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 selector field statement.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tname = strdup(tokens[0]);\n+\tif (!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+\tnew_fields = realloc(s->params.selector_field_names,\n+\t\t\t     (s->params.n_selector_fields + 1) * sizeof(char *));\n+\tif (!new_fields) {\n+\t\tfree(name);\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.selector_field_names = new_fields;\n+\ts->params.selector_field_names[s->params.n_selector_fields] = name;\n+\ts->params.n_selector_fields++;\n+\n+\treturn 0;\n+}\n+\n+static int\n+selector_block_parse(struct selector_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 << SELECTOR_SELECTOR_BLOCK))\n+\t\treturn selector_selector_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 << SELECTOR_BLOCK);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"group_id\")) {\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 group_id statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->params.group_id_field_name = strdup(tokens[1]);\n+\t\tif (!s->params.group_id_field_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\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"selector\"))\n+\t\treturn selector_selector_statement_parse(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+\n+\tif (!strcmp(tokens[0], \"member_id\")) {\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 member_id statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->params.member_id_field_name = strdup(tokens[1]);\n+\t\tif (!s->params.member_id_field_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\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"n_groups_max\")) {\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 n_groups statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->params.n_groups_max = 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 n_groups argument.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(tokens[0], \"n_members_per_group_max\")) {\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 n_members_per_group statement.\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\ts->params.n_members_per_group_max = 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 n_members_per_group 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@@ -1203,6 +1506,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \tstruct metadata_spec metadata_spec = {0};\n \tstruct action_spec action_spec = {0};\n \tstruct table_spec table_spec = {0};\n+\tstruct selector_spec selector_spec = {0};\n \tstruct regarray_spec regarray_spec = {0};\n \tstruct metarray_spec metarray_spec = {0};\n \tstruct apply_spec apply_spec = {0};\n@@ -1386,6 +1690,38 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/* selector block. */\n+\t\tif (block_mask & (1 << SELECTOR_BLOCK)) {\n+\t\t\tstatus = selector_block_parse(&selector_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 << SELECTOR_BLOCK))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* End of block. */\n+\t\t\tstatus = rte_swx_pipeline_selector_config(p,\n+\t\t\t\tselector_spec.name,\n+\t\t\t\t&selector_spec.params);\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 = \"Selector configuration error.\";\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n+\t\t\tselector_spec_free(&selector_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@@ -1544,6 +1880,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/* selector. */\n+\t\tif (!strcmp(tokens[0], \"selector\")) {\n+\t\t\tstatus = selector_statement_parse(&selector_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@@ -1651,6 +2002,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,\n \tmetadata_spec_free(&metadata_spec);\n \taction_spec_free(&action_spec);\n \ttable_spec_free(&table_spec);\n+\tselector_spec_free(&selector_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 a4d7d9788..ff0974c2e 100644\n--- a/lib/pipeline/version.map\n+++ b/lib/pipeline/version.map\n@@ -116,4 +116,17 @@ EXPERIMENTAL {\n \trte_swx_ctl_regarray_info_get;\n \trte_swx_pipeline_metarray_config;\n \trte_swx_pipeline_regarray_config;\n+\n+\t#added in 21.08\n+\trte_swx_pipeline_selector_config;\n+\trte_swx_ctl_pipeline_selector_fprintf;\n+\trte_swx_ctl_pipeline_selector_group_add;\n+\trte_swx_ctl_pipeline_selector_group_delete;\n+\trte_swx_ctl_pipeline_selector_group_member_add;\n+\trte_swx_ctl_pipeline_selector_group_member_delete;\n+\trte_swx_ctl_pipeline_selector_stats_read;\n+\trte_swx_ctl_selector_info_get;\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",
    "prefixes": [
        "V2",
        "3/5"
    ]
}