All of lore.kernel.org
 help / color / mirror / Atom feed
* [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands
@ 2021-07-02 20:49 Cristian Dumitrescu
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 " Cristian Dumitrescu
                   ` (4 more replies)
  0 siblings, 5 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 20:49 UTC (permalink / raw)
  To: dev; +Cc: Churchill Khangar

From: Churchill Khangar <churchill.khangar@intel.com>

For more felxibility, the single monolithic table update command is
split into table entry add, table entry delete, table default entry
add, pipeline commit and pipeline abort.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                   | 589 ++++++++++++++++------
 examples/pipeline/examples/vxlan.cli      |   3 +-
 examples/pipeline/examples/vxlan_pcap.cli |   3 +-
 3 files changed, 428 insertions(+), 167 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 215dd8e85..30754e319 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1038,25 +1038,76 @@ table_entry_free(struct rte_swx_table_entry *entry)
 	free(entry);
 }
 
-static const char cmd_pipeline_table_update_help[] =
-"pipeline <pipeline_name> table <table_name> update <file_name_add> "
-"<file_name_delete> <file_name_default>";
+#ifndef MAX_LINE_SIZE
+#define MAX_LINE_SIZE 2048
+#endif
+
+static int
+pipeline_table_entries_add(struct rte_swx_ctl_pipeline *p,
+			   const char *table_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_add(p,
+							      table_name,
+							      entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_table_add_help[] =
+"pipeline <pipeline_name> table <table_name> add <file_name>\n";
 
 static void
-cmd_pipeline_table_update(char **tokens,
-	uint32_t n_tokens,
-	char *out,
-	size_t out_size,
-	void *obj)
+cmd_pipeline_table_add(char **tokens,
+		       uint32_t n_tokens,
+		       char *out,
+		       size_t out_size,
+		       void *obj)
 {
 	struct pipeline *p;
-	char *pipeline_name, *table_name, *line = NULL;
-	char *file_name_add, *file_name_delete, *file_name_default;
-	FILE *file_add = NULL, *file_delete = NULL, *file_default = NULL;
-	uint32_t line_id;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
 	int status;
 
-	if (n_tokens != 8) {
+	if (n_tokens != 6) {
 		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
 		return;
 	}
@@ -1068,192 +1119,313 @@ cmd_pipeline_table_update(char **tokens,
 		return;
 	}
 
-	if (strcmp(tokens[2], "table") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "table");
+	table_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_table_entries_add(p->ctl,
+					    table_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_entries_delete(struct rte_swx_ctl_pipeline *p,
+			      const char *table_name,
+			      FILE *file,
+			      uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_delete(p,
+								 table_name,
+								 entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_delete_help[] =
+"pipeline <pipeline_name> table <table_name> delete <file_name>\n";
+
+static void
+cmd_pipeline_table_delete(char **tokens,
+			  uint32_t n_tokens,
+			  char *out,
+			  size_t out_size,
+			  void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
 	table_name = tokens[3];
 
-	if (strcmp(tokens[4], "update") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "update");
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
 		return;
 	}
 
-	file_name_add = tokens[5];
-	file_name_delete = tokens[6];
-	file_name_default = tokens[7];
+	status = pipeline_table_entries_delete(p->ctl,
+					       table_name,
+					       file,
+					       &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				 const char *table_name,
+				 FILE *file,
+				 uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
 
-	/* File open. */
-	if (strcmp(file_name_add, "none")) {
-		file_add = fopen(file_name_add, "r");
-		if (!file_add) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_add);
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
 			goto error;
 		}
-	}
 
-	if (strcmp(file_name_delete, "none")) {
-		file_delete = fopen(file_name_delete, "r");
-		if (!file_delete) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_delete);
+		status = rte_swx_ctl_pipeline_table_default_entry_add(p,
+								      table_name,
+								      entry);
+		table_entry_free(entry);
+		if (status)
 			goto error;
-		}
 	}
 
-	if (strcmp(file_name_default, "none")) {
-		file_default = fopen(file_name_default, "r");
-		if (!file_default) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_default);
-			goto error;
-		}
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_default_help[] =
+"pipeline <pipeline_name> table <table_name> default <file_name>\n";
+
+static void
+cmd_pipeline_table_default(char **tokens,
+			   uint32_t n_tokens,
+			   char *out,
+			   size_t out_size,
+			   void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
 	}
 
-	if (!file_add && !file_delete && !file_default) {
-		snprintf(out, out_size, "Nothing to be done.");
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
-	/* Buffer allocation. */
-	line = malloc(2048);
-	if (!line) {
-		snprintf(out, out_size, MSG_OUT_OF_MEMORY);
-		goto error;
-	}
-
-	/* Add. */
-	if (file_add)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
-
-			if (fgets(line, 2048, file_add) == NULL)
-				break;
-
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
-
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_add, line_id);
-				goto error;
-			}
+	table_name = tokens[3];
 
-			status = rte_swx_ctl_pipeline_table_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_add, line_id);
-				goto error;
-			}
-		}
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
 
+	status = pipeline_table_default_entry_add(p->ctl,
+						  table_name,
+						  file,
+						  &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
 
-	/* Delete. */
-	if (file_delete)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	fclose(file);
+}
 
-			if (fgets(line, 2048, file_delete) == NULL)
-				break;
+static const char cmd_pipeline_table_show_help[] =
+"pipeline <pipeline_name> table <table_name> show\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_table_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_delete, line_id);
-				goto error;
-			}
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_entry_delete(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status)  {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_delete, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-	/* Default. */
-	if (file_default)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	table_name = tokens[3];
+	status = rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
+}
 
-			if (fgets(line, 2048, file_default) == NULL)
-				break;
+static const char cmd_pipeline_commit_help[] =
+"pipeline <pipeline_name> commit\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_commit(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_default, line_id);
-				goto error;
-			}
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_default_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_default, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
 	status = rte_swx_ctl_pipeline_commit(p->ctl, 1);
-	if (status) {
-		snprintf(out, out_size, "Commit failed.");
-		goto error;
-	}
+	if (status)
+		snprintf(out, out_size, "Commit failed. "
+			"Use \"commit\" to retry or \"abort\" to discard the pending work.\n");
+}
 
+static const char cmd_pipeline_abort_help[] =
+"pipeline <pipeline_name> abort\n";
 
-	rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+static void
+cmd_pipeline_abort(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
 
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
-	return;
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-error:
 	rte_swx_ctl_pipeline_abort(p->ctl);
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
 }
 
 static const char cmd_pipeline_regrd_help[] =
@@ -1992,7 +2164,12 @@ cmd_help(char **tokens,
 			"\tpipeline port in\n"
 			"\tpipeline port out\n"
 			"\tpipeline build\n"
-			"\tpipeline table update\n"
+			"\tpipeline table add\n"
+			"\tpipeline table delete\n"
+			"\tpipeline table default\n"
+			"\tpipeline table show\n"
+			"\tpipeline commit\n"
+			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
 			"\tpipeline regwr\n"
 			"\tpipeline meter profile add\n"
@@ -2056,9 +2233,52 @@ cmd_help(char **tokens,
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 3) &&
 		(strcmp(tokens[1], "table") == 0) &&
-		(strcmp(tokens[2], "update") == 0)) {
+		(strcmp(tokens[2], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
 		snprintf(out, out_size, "\n%s\n",
-			cmd_pipeline_table_update_help);
+			cmd_pipeline_table_default_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_show_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "commit") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_commit_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "abort") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_abort_help);
 		return;
 	}
 
@@ -2216,9 +2436,48 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "add") == 0)) {
+			cmd_pipeline_table_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "delete") == 0)) {
+			cmd_pipeline_table_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_table_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_table_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 3) &&
+			(strcmp(tokens[2], "commit") == 0)) {
+			cmd_pipeline_commit(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
-			(strcmp(tokens[2], "table") == 0)) {
-			cmd_pipeline_table_update(tokens, n_tokens, out,
+			(strcmp(tokens[2], "abort") == 0)) {
+			cmd_pipeline_abort(tokens, n_tokens, out,
 				out_size, obj);
 			return;
 		}
diff --git a/examples/pipeline/examples/vxlan.cli b/examples/pipeline/examples/vxlan.cli
index 7bf4a5757..a3bde6a9f 100644
--- a/examples/pipeline/examples/vxlan.cli
+++ b/examples/pipeline/examples/vxlan.cli
@@ -22,6 +22,7 @@ pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/pipeline/examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/vxlan_pcap.cli b/examples/pipeline/examples/vxlan_pcap.cli
index 1636ba080..3cc9a94af 100644
--- a/examples/pipeline/examples/vxlan_pcap.cli
+++ b/examples/pipeline/examples/vxlan_pcap.cli
@@ -17,6 +17,7 @@ pipeline PIPELINE0 port out 3 sink none
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V2 1/5] examples/pipeline: improve table update CLI commands
  2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
@ 2021-07-02 22:39 ` Cristian Dumitrescu
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 2/5] table: add support for selector tables Cristian Dumitrescu
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:39 UTC (permalink / raw)
  To: dev; +Cc: Churchill Khangar

From: Churchill Khangar <churchill.khangar@intel.com>

For more felxibility, the single monolithic table update command is
split into table entry add, table entry delete, table default entry
add, pipeline commit and pipeline abort.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                   | 589 ++++++++++++++++------
 examples/pipeline/examples/vxlan.cli      |   3 +-
 examples/pipeline/examples/vxlan_pcap.cli |   3 +-
 3 files changed, 428 insertions(+), 167 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 215dd8e85..30754e319 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1038,25 +1038,76 @@ table_entry_free(struct rte_swx_table_entry *entry)
 	free(entry);
 }
 
-static const char cmd_pipeline_table_update_help[] =
-"pipeline <pipeline_name> table <table_name> update <file_name_add> "
-"<file_name_delete> <file_name_default>";
+#ifndef MAX_LINE_SIZE
+#define MAX_LINE_SIZE 2048
+#endif
+
+static int
+pipeline_table_entries_add(struct rte_swx_ctl_pipeline *p,
+			   const char *table_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_add(p,
+							      table_name,
+							      entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_table_add_help[] =
+"pipeline <pipeline_name> table <table_name> add <file_name>\n";
 
 static void
-cmd_pipeline_table_update(char **tokens,
-	uint32_t n_tokens,
-	char *out,
-	size_t out_size,
-	void *obj)
+cmd_pipeline_table_add(char **tokens,
+		       uint32_t n_tokens,
+		       char *out,
+		       size_t out_size,
+		       void *obj)
 {
 	struct pipeline *p;
-	char *pipeline_name, *table_name, *line = NULL;
-	char *file_name_add, *file_name_delete, *file_name_default;
-	FILE *file_add = NULL, *file_delete = NULL, *file_default = NULL;
-	uint32_t line_id;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
 	int status;
 
-	if (n_tokens != 8) {
+	if (n_tokens != 6) {
 		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
 		return;
 	}
@@ -1068,192 +1119,313 @@ cmd_pipeline_table_update(char **tokens,
 		return;
 	}
 
-	if (strcmp(tokens[2], "table") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "table");
+	table_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_table_entries_add(p->ctl,
+					    table_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_entries_delete(struct rte_swx_ctl_pipeline *p,
+			      const char *table_name,
+			      FILE *file,
+			      uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_delete(p,
+								 table_name,
+								 entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_delete_help[] =
+"pipeline <pipeline_name> table <table_name> delete <file_name>\n";
+
+static void
+cmd_pipeline_table_delete(char **tokens,
+			  uint32_t n_tokens,
+			  char *out,
+			  size_t out_size,
+			  void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
 	table_name = tokens[3];
 
-	if (strcmp(tokens[4], "update") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "update");
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
 		return;
 	}
 
-	file_name_add = tokens[5];
-	file_name_delete = tokens[6];
-	file_name_default = tokens[7];
+	status = pipeline_table_entries_delete(p->ctl,
+					       table_name,
+					       file,
+					       &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				 const char *table_name,
+				 FILE *file,
+				 uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
 
-	/* File open. */
-	if (strcmp(file_name_add, "none")) {
-		file_add = fopen(file_name_add, "r");
-		if (!file_add) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_add);
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
 			goto error;
 		}
-	}
 
-	if (strcmp(file_name_delete, "none")) {
-		file_delete = fopen(file_name_delete, "r");
-		if (!file_delete) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_delete);
+		status = rte_swx_ctl_pipeline_table_default_entry_add(p,
+								      table_name,
+								      entry);
+		table_entry_free(entry);
+		if (status)
 			goto error;
-		}
 	}
 
-	if (strcmp(file_name_default, "none")) {
-		file_default = fopen(file_name_default, "r");
-		if (!file_default) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_default);
-			goto error;
-		}
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_default_help[] =
+"pipeline <pipeline_name> table <table_name> default <file_name>\n";
+
+static void
+cmd_pipeline_table_default(char **tokens,
+			   uint32_t n_tokens,
+			   char *out,
+			   size_t out_size,
+			   void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
 	}
 
-	if (!file_add && !file_delete && !file_default) {
-		snprintf(out, out_size, "Nothing to be done.");
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
-	/* Buffer allocation. */
-	line = malloc(2048);
-	if (!line) {
-		snprintf(out, out_size, MSG_OUT_OF_MEMORY);
-		goto error;
-	}
-
-	/* Add. */
-	if (file_add)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
-
-			if (fgets(line, 2048, file_add) == NULL)
-				break;
-
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
-
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_add, line_id);
-				goto error;
-			}
+	table_name = tokens[3];
 
-			status = rte_swx_ctl_pipeline_table_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_add, line_id);
-				goto error;
-			}
-		}
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
 
+	status = pipeline_table_default_entry_add(p->ctl,
+						  table_name,
+						  file,
+						  &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
 
-	/* Delete. */
-	if (file_delete)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	fclose(file);
+}
 
-			if (fgets(line, 2048, file_delete) == NULL)
-				break;
+static const char cmd_pipeline_table_show_help[] =
+"pipeline <pipeline_name> table <table_name> show\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_table_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_delete, line_id);
-				goto error;
-			}
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_entry_delete(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status)  {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_delete, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-	/* Default. */
-	if (file_default)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	table_name = tokens[3];
+	status = rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
+}
 
-			if (fgets(line, 2048, file_default) == NULL)
-				break;
+static const char cmd_pipeline_commit_help[] =
+"pipeline <pipeline_name> commit\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_commit(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_default, line_id);
-				goto error;
-			}
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_default_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_default, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
 	status = rte_swx_ctl_pipeline_commit(p->ctl, 1);
-	if (status) {
-		snprintf(out, out_size, "Commit failed.");
-		goto error;
-	}
+	if (status)
+		snprintf(out, out_size, "Commit failed. "
+			"Use \"commit\" to retry or \"abort\" to discard the pending work.\n");
+}
 
+static const char cmd_pipeline_abort_help[] =
+"pipeline <pipeline_name> abort\n";
 
-	rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+static void
+cmd_pipeline_abort(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
 
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
-	return;
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-error:
 	rte_swx_ctl_pipeline_abort(p->ctl);
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
 }
 
 static const char cmd_pipeline_regrd_help[] =
@@ -1992,7 +2164,12 @@ cmd_help(char **tokens,
 			"\tpipeline port in\n"
 			"\tpipeline port out\n"
 			"\tpipeline build\n"
-			"\tpipeline table update\n"
+			"\tpipeline table add\n"
+			"\tpipeline table delete\n"
+			"\tpipeline table default\n"
+			"\tpipeline table show\n"
+			"\tpipeline commit\n"
+			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
 			"\tpipeline regwr\n"
 			"\tpipeline meter profile add\n"
@@ -2056,9 +2233,52 @@ cmd_help(char **tokens,
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 3) &&
 		(strcmp(tokens[1], "table") == 0) &&
-		(strcmp(tokens[2], "update") == 0)) {
+		(strcmp(tokens[2], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
 		snprintf(out, out_size, "\n%s\n",
-			cmd_pipeline_table_update_help);
+			cmd_pipeline_table_default_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_show_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "commit") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_commit_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "abort") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_abort_help);
 		return;
 	}
 
@@ -2216,9 +2436,48 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "add") == 0)) {
+			cmd_pipeline_table_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "delete") == 0)) {
+			cmd_pipeline_table_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_table_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_table_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 3) &&
+			(strcmp(tokens[2], "commit") == 0)) {
+			cmd_pipeline_commit(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
-			(strcmp(tokens[2], "table") == 0)) {
-			cmd_pipeline_table_update(tokens, n_tokens, out,
+			(strcmp(tokens[2], "abort") == 0)) {
+			cmd_pipeline_abort(tokens, n_tokens, out,
 				out_size, obj);
 			return;
 		}
diff --git a/examples/pipeline/examples/vxlan.cli b/examples/pipeline/examples/vxlan.cli
index 7bf4a5757..a3bde6a9f 100644
--- a/examples/pipeline/examples/vxlan.cli
+++ b/examples/pipeline/examples/vxlan.cli
@@ -22,6 +22,7 @@ pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/pipeline/examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/vxlan_pcap.cli b/examples/pipeline/examples/vxlan_pcap.cli
index 1636ba080..3cc9a94af 100644
--- a/examples/pipeline/examples/vxlan_pcap.cli
+++ b/examples/pipeline/examples/vxlan_pcap.cli
@@ -17,6 +17,7 @@ pipeline PIPELINE0 port out 3 sink none
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V2 2/5] table: add support for selector tables
  2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 " Cristian Dumitrescu
@ 2021-07-02 22:39 ` Cristian Dumitrescu
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 3/5] pipeline: " Cristian Dumitrescu
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:39 UTC (permalink / raw)
  To: dev

A selector table is made up of groups of weighted members, with a
given member potentially part of several groups. The select operation
returns a member ID by first selecting a group based on an input group
ID and then selecting a member within that group based on hashing one
or several input header/meta-data fields. It is very useful for
implementing an ECMP/WCMP-enabled FIB or a load balancer. It is part
of the action selector described by the P4 Portable Switch
Architecture (PSA) specification.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/table/meson.build              |   2 +
 lib/table/rte_swx_table_selector.c | 581 +++++++++++++++++++++++++++++
 lib/table/rte_swx_table_selector.h | 203 ++++++++++
 lib/table/version.map              |   8 +
 4 files changed, 794 insertions(+)
 create mode 100644 lib/table/rte_swx_table_selector.c
 create mode 100644 lib/table/rte_swx_table_selector.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index b7b70b805..16e55f086 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+	'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
         'rte_table_array.c',
@@ -20,6 +21,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+	'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
         'rte_table_acl.h',
diff --git a/lib/table/rte_swx_table_selector.c b/lib/table/rte_swx_table_selector.c
new file mode 100644
index 000000000..541ebc221
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.c
@@ -0,0 +1,581 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_selector.h"
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+struct group_member_info {
+	uint32_t member_id;
+	uint32_t member_weight;
+	uint32_t member_weight_normalized;
+	uint32_t count;
+};
+
+struct table {
+	/* Input parameters */
+	struct rte_swx_table_selector_params params;
+
+	/* Internal. */
+	uint32_t *group_table;
+	uint64_t group_table_size;
+	struct group_member_info *members;
+	uint32_t n_members_per_group_max_log2;
+};
+
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max)
+{
+	uint64_t group_table_size, members_size;
+
+	group_table_size = n_groups_max * n_members_per_group_max * sizeof(uint32_t);
+
+	members_size = n_members_per_group_max * sizeof(struct group_member_info);
+
+	return sizeof(struct table) + group_table_size + members_size;
+}
+
+void
+rte_swx_table_selector_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	free(t->members);
+
+	env_free(t->group_table, t->group_table_size);
+
+	free(t->params.selector_mask);
+
+	free(t);
+}
+
+static int
+table_create_check(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return -1;
+
+	if (!params->selector_size ||
+	    (params->selector_size > 64) ||
+	    !params->n_groups_max ||
+	    (params->n_groups_max > 1U << 31) ||
+	    !params->n_members_per_group_max ||
+	    (params->n_members_per_group_max > 1U << 31))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+table_params_copy(struct table *t, struct rte_swx_table_selector_params *params)
+{
+	uint32_t selector_size, i;
+
+	selector_size = rte_align32pow2(params->selector_size);
+	if (selector_size < 8)
+		selector_size = 8;
+
+	memcpy(&t->params, params, sizeof(struct rte_swx_table_selector_params));
+	t->params.selector_size = selector_size;
+	t->params.selector_mask = NULL;
+	t->params.n_groups_max = rte_align32pow2(params->n_groups_max);
+	t->params.n_members_per_group_max = rte_align32pow2(params->n_members_per_group_max);
+
+	for (i = 0; i < 32; i++)
+		if (params->n_members_per_group_max == 1U << i)
+			t->n_members_per_group_max_log2 = i;
+
+	/* t->params.selector_mask */
+	t->params.selector_mask = calloc(selector_size, sizeof(uint8_t));
+	if (!t->params.selector_mask)
+		goto error;
+
+	if (params->selector_mask)
+		memcpy(t->params.selector_mask, params->selector_mask, params->selector_size);
+	else
+		memset(t->params.selector_mask, 0xFF, params->selector_size);
+
+	return 0;
+
+error:
+	free(t->params.selector_mask);
+	t->params.selector_mask = NULL;
+
+	return -ENOMEM;
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group);
+
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node)
+{
+	struct table *t = NULL;
+	uint32_t group_size, i;
+	int status;
+
+	/* Check input arguments. */
+	status = table_create_check(params);
+	if (status)
+		goto error;
+
+	/* Table object. */
+	t = calloc(1, sizeof(struct table));
+	if (!t)
+		goto error;
+
+	/* Parameter copy. */
+	status = table_params_copy(t, params);
+	if (status)
+		goto error;
+
+	/* Group. */
+	group_size = params->n_members_per_group_max * sizeof(uint32_t);
+	t->group_table_size = params->n_groups_max * group_size;
+
+	t->group_table = env_calloc(t->group_table_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t->group_table)
+		goto error;
+
+	t->members = calloc(params->n_members_per_group_max, sizeof(struct group_member_info));
+	if (!t->members)
+		goto error;
+
+	if (groups)
+		for (i = 0; i < params->n_groups_max; i++)
+			if (groups[i]) {
+				status = group_set(t, i, groups[i]);
+				if (status)
+					goto error;
+			}
+
+	return t;
+
+error:
+	rte_swx_table_selector_free(t);
+	return NULL;
+}
+
+
+static int
+group_check(struct table *t, struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct rte_swx_table_selector_member *e;
+		uint32_t n = 0;
+
+		/* Check group size. */
+		if (n_members >= t->params.n_members_per_group_max)
+			return -ENOSPC;
+
+		/* Check attributes of the current group member. */
+		if (elem->member_id >= t->params.n_members_per_group_max ||
+		    !elem->member_weight)
+			return -ENOSPC;
+
+		/* Check against duplicate member IDs. */
+		TAILQ_FOREACH(e, &group->members, node)
+			if (e->member_id == elem->member_id)
+				n++;
+
+		if (n != 1)
+			return -EINVAL;
+
+		/* Update group size. */
+		n_members++;
+	}
+
+	return 0;
+}
+
+static uint32_t
+members_read(struct group_member_info *members,
+	     struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct group_member_info *m = &members[n_members];
+
+		memset(m, 0, sizeof(struct group_member_info));
+
+		m->member_id = elem->member_id;
+		m->member_weight = elem->member_weight;
+		m->member_weight_normalized = elem->member_weight;
+
+		n_members++;
+	}
+
+	return n_members;
+}
+
+static uint32_t
+members_min_weight_find(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t min = UINT32_MAX, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight < min)
+			min = m->member_weight;
+	}
+
+	return min;
+}
+
+static uint32_t
+members_weight_divisor_check(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight_normalized % divisor)
+			return 0; /* FALSE. */
+	}
+
+	return 1; /* TRUE. */
+}
+
+static void
+members_weight_divisor_apply(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->member_weight_normalized /= divisor;
+	}
+}
+
+static uint32_t
+members_weight_sum(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t result = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		result += m->member_weight_normalized;
+	}
+
+	return result;
+}
+
+static void
+members_weight_scale(struct group_member_info *members,
+		     uint32_t n_members,
+		     uint32_t n_members_per_group_max,
+		     uint32_t weight_sum)
+{
+	uint32_t multiplier, remainder, i;
+
+	multiplier = n_members_per_group_max / weight_sum;
+	remainder = n_members_per_group_max % weight_sum;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->count = m->member_weight_normalized * multiplier;
+	}
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t min;
+
+		min = m->member_weight_normalized;
+		if (remainder < m->member_weight_normalized)
+			min = remainder;
+
+		m->count += min;
+		remainder -= min;
+		if (!remainder)
+			break;
+	}
+}
+
+static void
+members_write(struct group_member_info *members,
+	      uint32_t n_members,
+	      uint32_t *group_table)
+{
+	uint32_t pos = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t j;
+
+		for (j = 0; j < m->count; j++)
+			group_table[pos++] = m->member_id;
+	}
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group)
+{
+	uint32_t *gt = &t->group_table[group_id * t->params.n_members_per_group_max];
+	struct group_member_info *members = t->members;
+	uint32_t n_members, weight_min, weight_sum, divisor;
+	int status = 0;
+
+	/* Check input arguments. */
+	if (group_id >= t->params.n_groups_max)
+		return -EINVAL;
+
+	status = group_check(t, group);
+	if (status)
+		return status;
+
+	/* Read group members. */
+	n_members = members_read(members, group);
+
+	if (!n_members) {
+		memset(gt, 0, t->params.n_members_per_group_max * sizeof(uint32_t));
+
+		return 0;
+	}
+
+	/* Normalize weights. */
+	weight_min = members_min_weight_find(members, n_members);
+
+	for (divisor = 2; divisor <= weight_min; divisor++)
+		if (members_weight_divisor_check(members, n_members, divisor))
+			members_weight_divisor_apply(members, n_members, divisor);
+
+	/* Scale weights. */
+	weight_sum = members_weight_sum(members, n_members);
+	if (weight_sum > t->params.n_members_per_group_max)
+		return -ENOSPC;
+
+	members_weight_scale(members, n_members, t->params.n_members_per_group_max, weight_sum);
+
+	/* Write group members to the group table. */
+	members_write(members, n_members, gt);
+
+	return 0;
+}
+
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group)
+{
+	struct table *t = table;
+
+	return group_set(t, group_id, group);
+}
+
+struct mailbox {
+
+};
+
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox __rte_unused,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer)
+{
+	struct table *t = table;
+	uint32_t *group_id_ptr, *member_id_ptr, group_id, member_id, selector, group_member_index;
+
+	group_id_ptr = (uint32_t *)&(*group_id_buffer)[t->params.group_id_offset];
+
+	member_id_ptr = (uint32_t *)&(*member_id_buffer)[t->params.member_id_offset];
+
+	group_id = *group_id_ptr & (t->params.n_groups_max - 1);
+
+	selector = hash(&(*selector_buffer)[t->params.selector_offset],
+			t->params.selector_mask,
+			t->params.selector_size,
+			0);
+
+	group_member_index = selector & (t->params.n_members_per_group_max - 1);
+
+	member_id = t->group_table[(group_id << t->n_members_per_group_max_log2) +
+				   group_member_index];
+
+	*member_id_ptr = member_id;
+
+	return 1;
+}
diff --git a/lib/table/rte_swx_table_selector.h b/lib/table/rte_swx_table_selector.h
new file mode 100644
index 000000000..71b6a7481
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+#define __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Selector Table
+ *
+ * Selector table interface.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+#include "rte_swx_table.h"
+
+/** Selector table creation parameters. */
+struct rte_swx_table_selector_params {
+	/** Group ID offset. */
+	uint32_t group_id_offset;
+
+	/** Selector size in bytes. Must be non-zero. */
+	uint32_t selector_size;
+
+	/** Offset of the first byte of the selector within the selector buffer. */
+	uint32_t selector_offset;
+
+	/** Mask of *selector_size* bytes logically laid over the bytes at positions
+	 * selector_offset* .. (*selector_offset* + *selector_size* - 1) of the selector buffer in
+	 * order to specify which bits from the selector buffer are part of the selector and which
+	 * ones are not. A bit value of 1 in the *selector_mask* means the respective bit in the
+	 * selector buffer is part of the selector, while a bit value of 0 means the opposite. A
+	 * NULL value means that all the bits are part of the selector, i.e. the *selector_mask*
+	 * is an all-ones mask.
+	 */
+	uint8_t *selector_mask;
+
+	/** Member ID offset. */
+	uint32_t member_id_offset;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/** Group member parameters. */
+struct rte_swx_table_selector_member {
+	/** Linked list connectivity. */
+	TAILQ_ENTRY(rte_swx_table_selector_member) node;
+
+	/** Member ID. */
+	uint32_t member_id;
+
+	/** Member weight. */
+	uint32_t member_weight;
+};
+
+/** List of group members. */
+TAILQ_HEAD(rte_swx_table_selector_member_list, rte_swx_table_selector_member);
+
+/** Group parameters. */
+struct rte_swx_table_selector_group {
+	/** List of group members. */
+	struct rte_swx_table_selector_member_list members;
+};
+
+/**
+ * Selector table memory footprint get
+ *
+ * @param[in] n_groups_max
+ *   Maximum number of groups. Must be non-zero.
+ * @param[in] n_members_per_group_max
+ *   Maximum number of members per group. Must be non-zero.
+ * @return
+ *   Selector table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max);
+
+/**
+ * Selector table mailbox size get
+ *
+ * The mailbox is used to store the context of a select operation that is in
+ * progress and it is passed as a parameter to the select operation. This allows
+ * for multiple concurrent select operations into the same table.
+ *
+ * @return
+ *   Selector table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void);
+
+/**
+ * Selector table create
+ *
+ * @param[in] params
+ *   Selector table creation parameters.
+ * @param[in] groups
+ *   Groups to be added to the table at creation time. When NULL, it signifies that all groups are
+ *   invalid, otherwise it points to a pre-allocated array of size *n_groups_max*, where a NULL
+ *   element indicates that the associated group is invalid.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node);
+
+/**
+ * Group set
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] group_id
+ *   Group ID.
+ * @param[in] group
+ *   Group parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument(s);
+ *   -ENOSPC: Too many group members.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group);
+
+/**
+ * Selector table select
+ *
+ * This operation selects a member from the given group based on a hasing scheme.
+ *
+ * Multiple invocations of this function may be required in order to complete a single select
+ * operation for a given table and a given group ID. The completion of the operation is flagged by
+ * a return value of 1; in case of a return value of 0, the function must be invoked again with
+ * exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of each on-going  operation. The mailbox
+ * mechanism allows for multiple concurrent select operations into the same table.
+ *
+ * The typical reason an implementation may choose to split the operation into multiple steps is to
+ * hide the latency of the inherrent memory read operations: before a read operation with the
+ * source data likely not in the CPU cache, the source data prefetch is issued and the operation is
+ * postponed in favor of some other unrelated work, which the CPU executes in parallel with the
+ * source data being fetched into the CPU cache; later on, the operation is resumed, this time with
+ * the source data likely to be read from the CPU cache with no CPU pipeline stall, which
+ * significantly improves the operation performance.
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] group_id_buffer
+ *   Buffer where the input group ID is located at offset *group_id_offset*.
+ * @param[in] selector_buffer
+ *   Buffer where the key to select a member within the identified group is located starting from
+ *   offset *selector_offset*. Its size must be equal to the table *selector_size*.
+ * @param[in] member_id_buffer
+ *   Buffer where the output member ID is to be placed at offset *member_id_offset*.
+ * @return
+ *   0 when the operation is not yet completed, and 1 when the operation is complete. No other
+ *   return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer);
+
+/**
+ * Selector table free
+ *
+ * @param[in] table
+ *   Selector table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_selector_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index eb0291ac4..29301480c 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -28,4 +28,12 @@ EXPERIMENTAL {
 
 	# added in 21.05
 	rte_swx_table_wildcard_match_ops;
+
+	# added in 21.08
+	rte_swx_table_selector_create;
+	rte_swx_table_selector_footprint_get;
+	rte_swx_table_selector_free;
+	rte_swx_table_selector_group_set;
+	rte_swx_table_selector_mailbox_size_get;
+	rte_swx_table_selector_select;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V2 3/5] pipeline: add support for selector tables
  2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 " Cristian Dumitrescu
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 2/5] table: add support for selector tables Cristian Dumitrescu
@ 2021-07-02 22:40 ` Cristian Dumitrescu
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 4/5] examples/pipeline: " Cristian Dumitrescu
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 5/5] examples/pipeline: add selector example Cristian Dumitrescu
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:40 UTC (permalink / raw)
  To: dev

Add pipeline-level support for selector tables,

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.c           | 700 ++++++++++++++++++++++++-
 lib/pipeline/rte_swx_ctl.h           | 253 +++++++++
 lib/pipeline/rte_swx_pipeline.c      | 748 ++++++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h      |  51 ++
 lib/pipeline/rte_swx_pipeline_spec.c | 354 ++++++++++++-
 lib/pipeline/version.map             |  13 +
 6 files changed, 2034 insertions(+), 85 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c
index 5d04e750f..8cabce2b9 100644
--- a/lib/pipeline/rte_swx_ctl.c
+++ b/lib/pipeline/rte_swx_ctl.c
@@ -10,6 +10,8 @@
 #include <rte_common.h>
 #include <rte_byteorder.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_ctl.h"
 
 #define CHECK(condition, err_code)                                             \
@@ -89,11 +91,44 @@ struct table {
 	uint32_t n_delete;
 };
 
+struct selector {
+	/* Selector table info. */
+	struct rte_swx_ctl_selector_info info;
+
+	/* group_id field. */
+	struct rte_swx_ctl_table_match_field_info group_id_field;
+
+	/* selector fields. */
+	struct rte_swx_ctl_table_match_field_info *selector_fields;
+
+	/* member_id field. */
+	struct rte_swx_ctl_table_match_field_info member_id_field;
+
+	/* Current selector table. Array of info.n_groups_max elements.*/
+	struct rte_swx_table_selector_group **groups;
+
+	/* Pending selector table subject to the next commit. Array of info.n_groups_max elements.
+	 */
+	struct rte_swx_table_selector_group **pending_groups;
+
+	/* Valid flag per group. Array of n_groups_max elements. */
+	int *groups_added;
+
+	/* Pending delete flag per group. Group deletion is subject to the next commit. Array of
+	 * info.n_groups_max elements.
+	 */
+	int *groups_pending_delete;
+
+	/* Params. */
+	struct rte_swx_table_selector_params params;
+};
+
 struct rte_swx_ctl_pipeline {
 	struct rte_swx_ctl_pipeline_info info;
 	struct rte_swx_pipeline *p;
 	struct action *actions;
 	struct table *tables;
+	struct selector *selectors;
 	struct rte_swx_table_state *ts;
 	struct rte_swx_table_state *ts_next;
 	int numa_node;
@@ -709,6 +744,209 @@ table_free(struct rte_swx_ctl_pipeline *ctl)
 	ctl->tables = NULL;
 }
 
+static void
+selector_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->groups[group_id] = NULL;
+}
+
+static void
+selector_pending_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->pending_groups[group_id] = NULL;
+}
+
+static int
+selector_group_duplicate_to_pending(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *g, *gp;
+	struct rte_swx_table_selector_member *m;
+
+	selector_pending_group_members_free(s, group_id);
+
+	g = s->groups[group_id];
+	gp = s->pending_groups[group_id];
+
+	if (!gp) {
+		gp = calloc(1, sizeof(struct rte_swx_table_selector_group));
+		if (!gp)
+			goto error;
+
+		TAILQ_INIT(&gp->members);
+
+		s->pending_groups[group_id] = gp;
+	}
+
+	if (!g)
+		return 0;
+
+	TAILQ_FOREACH(m, &g->members, node) {
+		struct rte_swx_table_selector_member *mp;
+
+		mp = calloc(1, sizeof(struct rte_swx_table_selector_member));
+		if (!mp)
+			goto error;
+
+		memcpy(mp, m, sizeof(struct rte_swx_table_selector_member));
+
+		TAILQ_INSERT_TAIL(&gp->members, mp, node);
+	}
+
+	return 0;
+
+error:
+	selector_pending_group_members_free(s, group_id);
+	return -ENOMEM;
+}
+
+static void
+selector_free(struct rte_swx_ctl_pipeline *ctl)
+{
+	uint32_t i;
+
+	if (ctl->selectors)
+		return;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t i;
+
+		/* selector_fields. */
+		free(s->selector_fields);
+
+		/* groups. */
+		if (s->groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_group_members_free(s, i);
+
+		free(s->groups);
+
+		/* pending_groups. */
+		if (s->pending_groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_pending_group_members_free(s, i);
+
+		free(s->pending_groups);
+
+		/* groups_added. */
+		free(s->groups_added);
+
+		/* groups_pending_delete. */
+		free(s->groups_pending_delete);
+
+		/* params. */
+		free(s->params.selector_mask);
+	}
+
+	free(ctl->selectors);
+	ctl->selectors = NULL;
+}
+
+static struct selector *
+selector_find(struct rte_swx_ctl_pipeline *ctl, const char *selector_name)
+{
+	uint32_t i;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+
+		if (!strcmp(selector_name, s->info.name))
+			return s;
+	}
+
+	return NULL;
+}
+
+static int
+selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_ctl_table_match_field_info *first = NULL, *last = NULL;
+	uint8_t *selector_mask = NULL;
+	uint32_t selector_size = 0, selector_offset = 0, i;
+
+	/* Find first (smallest offset) and last (biggest offset) match fields. */
+	first = &s->selector_fields[0];
+	last = &s->selector_fields[0];
+
+	for (i = 1; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* selector_offset. */
+	selector_offset = first->offset / 8;
+
+	/* selector_size. */
+	selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* selector_mask. */
+	selector_mask = calloc(1, selector_size);
+	if (!selector_mask)
+		return -ENOMEM;
+
+	for (i = 0; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+		uint32_t start;
+		size_t size;
+
+		start = (f->offset - first->offset) / 8;
+		size = f->n_bits / 8;
+
+		memset(&selector_mask[start], 0xFF, size);
+	}
+
+	/* Fill in. */
+	s->params.group_id_offset = s->group_id_field.offset / 8;
+	s->params.selector_size = selector_size;
+	s->params.selector_offset = selector_offset;
+	s->params.selector_mask = selector_mask;
+	s->params.member_id_offset = s->member_id_field.offset / 8;
+	s->params.n_groups_max = s->info.n_groups_max;
+	s->params.n_members_per_group_max = s->info.n_members_per_group_max;
+
+	return 0;
+}
+
 static void
 table_state_free(struct rte_swx_ctl_pipeline *ctl)
 {
@@ -730,6 +968,15 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)
 			table->ops.free(ts->obj);
 	}
 
+	/* For each selector table, free its table state. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct rte_swx_table_state *ts = &ctl->ts_next[i];
+
+		/* Table object. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
 	free(ctl->ts_next);
 	ctl->ts_next = NULL;
 }
@@ -740,13 +987,14 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 	int status = 0;
 	uint32_t i;
 
-	ctl->ts_next = calloc(ctl->info.n_tables,
+	ctl->ts_next = calloc(ctl->info.n_tables + ctl->info.n_selectors,
 			      sizeof(struct rte_swx_table_state));
 	if (!ctl->ts_next) {
 		status = -ENOMEM;
 		goto error;
 	}
 
+	/* Tables. */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		struct table *table = &ctl->tables[i];
 		struct rte_swx_table_state *ts = &ctl->ts[i];
@@ -782,6 +1030,19 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 		ts_next->default_action_id = ts->default_action_id;
 	}
 
+	/* Selector tables. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + i];
+
+		/* Table object. */
+		ts_next->obj = rte_swx_table_selector_create(&s->params, NULL, ctl->numa_node);
+		if (!ts_next->obj) {
+			status = -ENODEV;
+			goto error;
+		}
+	}
+
 	return 0;
 
 error:
@@ -799,6 +1060,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)
 
 	table_state_free(ctl);
 
+	selector_free(ctl);
+
 	table_free(ctl);
 
 	free(ctl);
@@ -940,6 +1203,77 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)
 			goto error;
 	}
 
+	/* selector tables. */
+	ctl->selectors = calloc(ctl->info.n_selectors, sizeof(struct selector));
+	if (!ctl->selectors)
+		goto error;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t j;
+
+		/* info. */
+		status = rte_swx_ctl_selector_info_get(p, i, &s->info);
+		if (status)
+			goto error;
+
+		/* group_id field. */
+		status = rte_swx_ctl_selector_group_id_field_info_get(p,
+			i,
+			&s->group_id_field);
+		if (status)
+			goto error;
+
+		/* selector fields. */
+		s->selector_fields = calloc(s->info.n_selector_fields,
+			sizeof(struct rte_swx_ctl_table_match_field_info));
+		if (!s->selector_fields)
+			goto error;
+
+		for (j = 0; j < s->info.n_selector_fields; j++) {
+			status = rte_swx_ctl_selector_field_info_get(p,
+				i,
+				j,
+				&s->selector_fields[j]);
+			if (status)
+				goto error;
+		}
+
+		/* member_id field. */
+		status = rte_swx_ctl_selector_member_id_field_info_get(p,
+			i,
+			&s->member_id_field);
+		if (status)
+			goto error;
+
+		/* groups. */
+		s->groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->groups)
+			goto error;
+
+		/* pending_groups. */
+		s->pending_groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->pending_groups)
+			goto error;
+
+		/* groups_added. */
+		s->groups_added = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_added)
+			goto error;
+
+		/* groups_pending_delete. */
+		s->groups_pending_delete = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_pending_delete)
+			goto error;
+
+		/* params. */
+		status = selector_params_get(ctl, i);
+		if (status)
+			goto error;
+	}
+
 	/* ts. */
 	status = rte_swx_pipeline_table_state_get(p, &ctl->ts);
 	if (status)
@@ -1499,6 +1833,295 @@ table_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)
 	table_pending_default_free(table);
 }
 
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id)
+{
+	struct selector *s;
+	uint32_t i;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0] || !group_id)
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Find an unused group. */
+	for (i = 0; i < s->info.n_groups_max; i++)
+		if (!s->groups_added[i]) {
+			*group_id = i;
+			s->groups_added[i] = 1;
+			return 0;
+		}
+
+	return -ENOSPC;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id])
+		return -EINVAL;
+
+	/* Check if this group is already scheduled for deletion. */
+	if (s->groups_pending_delete[group_id])
+		return 0;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Schedule removal of all the members from the current group. */
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	/* Schedule the group for deletion. */
+	s->groups_pending_delete[group_id] = 1;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	if (!member_weight)
+		return rte_swx_ctl_pipeline_selector_group_member_delete(ctl,
+									 selector_name,
+									 group_id,
+									 member_id);
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id] ||
+	   s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* If this member is already in this group, then simply update its weight and return. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			m->member_weight = member_weight;
+			return 0;
+		}
+
+	/* Add new member to this group. */
+	m = calloc(1, sizeof(struct rte_swx_table_selector_member));
+	if (!m)
+		return -ENOMEM;
+
+	m->member_id = member_id;
+	m->member_weight = member_weight;
+
+	TAILQ_INSERT_TAIL(&group->members, m, node);
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id __rte_unused,
+						  uint32_t member_id __rte_unused)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	    (group_id >= s->info.n_groups_max) ||
+	    !s->groups_added[group_id] ||
+	    s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Look for this member in the group and remove it, if found. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			TAILQ_REMOVE(&group->members, m, node);
+			free(m);
+			return 0;
+		}
+
+	return 0;
+}
+
+static int
+selector_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Push pending group member changes (s->pending_groups[group_id]) to the selector table
+	 * mirror copy (ts_next->obj).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+		int status;
+
+		/* Skip this group if no change needed. */
+		if (!group)
+			continue;
+
+		/* Apply the pending changes for the current group. */
+		status = rte_swx_table_selector_group_set(ts_next->obj, group_id, group);
+		if (status)
+			return status;
+	}
+
+	return 0;
+}
+
+static void
+selector_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Commit pending group member changes (s->pending_groups[group_id]) to the stable group
+	 * records (s->groups[group_id).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *g = s->groups[group_id];
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		/* Skip this group if no change needed. */
+		if (!gp)
+			continue;
+
+		/* Transition the pending changes to stable. */
+		s->groups[group_id] = gp;
+		s->pending_groups[group_id] = NULL;
+
+		/* Free the old group member list. */
+		if (!g)
+			continue;
+
+		for ( ; ; ) {
+			struct rte_swx_table_selector_member *m;
+
+			m = TAILQ_FIRST(&g->members);
+			if (!m)
+				break;
+
+			TAILQ_REMOVE(&g->members, m, node);
+			free(m);
+		}
+
+		free(g);
+	}
+
+	/* Commit pending group validity changes (from s->groups_pending_delete[group_id] to
+	 * s->groups_added[group_id].
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		if (s->groups_pending_delete[group_id]) {
+			s->groups_added[group_id] = 0;
+			s->groups_pending_delete[group_id] = 0;
+		}
+}
+
+static void
+selector_rollback(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts = &ctl->ts[ctl->info.n_tables + selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Discard any previous changes to the selector table mirror copy (ts_next->obj). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		if (gp) {
+			ts_next->obj = ts->obj;
+			break;
+		}
+	}
+}
+
+static void
+selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Discard any pending group member changes (s->pending_groups[group_id]). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		selector_pending_group_members_free(s, group_id);
+
+	/* Discard any pending group deletions. */
+	memset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));
+}
+
 int
 rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 {
@@ -1508,8 +2131,8 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 
 	CHECK(ctl, EINVAL);
 
-	/* Operate the changes on the current ts_next before it becomes the new
-	 * ts.
+	/* Operate the changes on the current ts_next before it becomes the new ts. First, operate
+	 * all the changes that can fail; if no failure, then operate the changes that cannot fail.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		status = table_rollfwd0(ctl, i, 0);
@@ -1517,6 +2140,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			goto rollback;
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		status = selector_rollfwd(ctl, i);
+		if (status)
+			goto rollback;
+	}
+
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_rollfwd1(ctl, i);
 
@@ -1529,7 +2158,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 	ctl->ts = ctl->ts_next;
 	ctl->ts_next = ts;
 
-	/* Operate the changes on the current ts_next, which is the previous ts.
+	/* Operate the changes on the current ts_next, which is the previous ts, in order to get
+	 * the current ts_next in sync with the current ts. Since the changes that can fail did
+	 * not fail on the previous ts_next, it is guaranteed that they will not fail on the
+	 * current ts_next, hence no error checking is needed.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		table_rollfwd0(ctl, i, 1);
@@ -1537,6 +2169,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 		table_rollfwd2(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollfwd(ctl, i);
+		selector_rollfwd_finalize(ctl, i);
+	}
+
 	return 0;
 
 rollback:
@@ -1546,6 +2183,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			table_abort(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollback(ctl, i);
+		if (abort_on_fail)
+			selector_abort(ctl, i);
+	}
+
 	return status;
 }
 
@@ -1559,6 +2202,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)
 
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_abort(ctl, i);
+
+	for (i = 0; i < ctl->info.n_selectors; i++)
+		selector_abort(ctl, i);
 }
 
 static int
@@ -1858,3 +2504,49 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 		n_entries);
 	return 0;
 }
+
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name)
+{
+	struct selector *s;
+	uint32_t group_id;
+
+	if (!f || !ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Selector. */
+	fprintf(f, "# Selector %s: max groups %u, max members per group %u\n",
+		s->info.name,
+		s->info.n_groups_max,
+		s->info.n_members_per_group_max);
+
+	/* Groups. */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->groups[group_id];
+		struct rte_swx_table_selector_member *m;
+		uint32_t n_members = 0;
+
+		fprintf(f, "Group %u = [", group_id);
+
+		/* Non-empty group. */
+		if (group)
+			TAILQ_FOREACH(m, &group->members, node) {
+				fprintf(f, "%u:%u ", m->member_id, m->member_weight);
+				n_members++;
+			}
+
+		/* Empty group. */
+		if (!n_members)
+			fprintf(f, "0:1 ");
+
+		fprintf(f, "]\n");
+	}
+
+	return 0;
+}
diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index dee788be8..f37301cf9 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -22,6 +22,7 @@ extern "C" {
 
 #include "rte_swx_port.h"
 #include "rte_swx_table.h"
+#include "rte_swx_table_selector.h"
 
 struct rte_swx_pipeline;
 
@@ -48,6 +49,9 @@ struct rte_swx_ctl_pipeline_info {
 	/** Number of tables. */
 	uint32_t n_tables;
 
+	/** Number of selector tables. */
+	uint32_t n_selectors;
+
 	/** Number of register arrays. */
 	uint32_t n_regarrays;
 
@@ -385,6 +389,129 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 				      const char *table_name,
 				      struct rte_swx_table_stats *stats);
 
+/*
+ * Selector Table Query API.
+ */
+
+/** Selector info. */
+struct rte_swx_ctl_selector_info {
+	/** Selector table name. */
+	char name[RTE_SWX_CTL_NAME_SIZE];
+
+	/** Number of selector fields. */
+	uint32_t n_selector_fields;
+
+	/** Maximum number of groups. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Selector table info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors* - 1).
+ * @param[out] selector
+ *   Selector table info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector);
+
+/**
+ * Selector table "group ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "group ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+					     uint32_t selector_id,
+					     struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Sselector table selector field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[in] selector_field_id
+ *   Selector table selector field ID (0 .. *n_selector_fields* - 1).
+ * @param[out] field
+ *   Selector table selector field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+				    uint32_t selector_id,
+				    uint32_t selector_field_id,
+				    struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Selector table "member ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "member ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+					      uint32_t selector_id,
+					      struct rte_swx_ctl_table_match_field_info *field);
+
+/** Selector table statistics. */
+struct rte_swx_pipeline_selector_stats {
+	/** Number of packets. */
+	uint64_t n_pkts;
+};
+
+/**
+ * Selector table statistics counters read
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] stats
+ *   Selector table stats. Must point to a pre-allocated structure.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+					 const char *selector_name,
+					 struct rte_swx_pipeline_selector_stats *stats);
+
 /*
  * Table Update API.
  */
@@ -529,6 +656,111 @@ rte_swx_ctl_pipeline_table_entry_delete(struct rte_swx_ctl_pipeline *ctl,
 					const char *table_name,
 					struct rte_swx_table_entry *entry);
 
+/**
+ * Pipeline selector table group add
+ *
+ * Add a new group to a selector table. This operation is executed before this
+ * function returns and its result is independent of the result of the next
+ * commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] group_id
+ *   The ID of the new group. Only valid when the function call is successful.
+ *   This group is initially empty, i.e. it does not contain any members.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOSPC: All groups are currently in use, no group available.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id);
+
+/**
+ * Pipeline selector table group delete
+ *
+ * Schedule a group for deletion as part of the next commit operation. The group
+ * to be deleted can be empty or non-empty.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   Group to be deleted from the selector table.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id);
+
+/**
+ * Pipeline selector table member add to group
+ *
+ * Schedule the operation to add a new member to an existing group as part of
+ * the next commit operation. If this member is already in this group, the
+ * member weight is updated to the new value. A weight of zero means this member
+ * is to be deleted from the group.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID.
+ * @param[in] member_id
+ *   The member to be added to the group.
+ * @param[in] member_weight
+ *   Member weight.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory;
+ *   -ENOSPC: The group is full.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight);
+
+/**
+ * Pipeline selector table member delete from group
+ *
+ * Schedule the operation to delete a member from an existing group as part of
+ * the next commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID. Must be valid.
+ * @param[in] member_id
+ *   The member to be added to the group. Must be valid.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id,
+						  uint32_t member_id);
+
 /**
  * Pipeline commit
  *
@@ -608,6 +840,27 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 				   struct rte_swx_ctl_pipeline *ctl,
 				   const char *table_name);
 
+/**
+ * Pipeline selector print to file
+ *
+ * Print all the selector entries to file.
+ *
+ * @param[in] f
+ *   Output file.
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name);
+
 /*
  * Register Array Query API.
  */
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index a2732a1e5..22c860f28 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -15,6 +15,8 @@
 #include <rte_cycles.h>
 #include <rte_meter.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_pipeline.h"
 #include "rte_swx_ctl.h"
 
@@ -498,6 +500,7 @@ enum instruction_type {
 
 	/* table TABLE */
 	INSTR_TABLE,
+	INSTR_SELECTOR,
 
 	/* extern e.obj.func */
 	INSTR_EXTERN_OBJ,
@@ -794,6 +797,38 @@ struct table_statistics {
 	uint64_t *n_pkts_action;
 };
 
+/*
+ * Selector.
+ */
+struct selector {
+	TAILQ_ENTRY(selector) node;
+	char name[RTE_SWX_NAME_SIZE];
+
+	struct field *group_id_field;
+	struct field **selector_fields;
+	uint32_t n_selector_fields;
+	struct header *selector_header;
+	struct field *member_id_field;
+
+	uint32_t n_groups_max;
+	uint32_t n_members_per_group_max;
+
+	uint32_t id;
+};
+
+TAILQ_HEAD(selector_tailq, selector);
+
+struct selector_runtime {
+	void *mailbox;
+	uint8_t **group_id_buffer;
+	uint8_t **selector_buffer;
+	uint8_t **member_id_buffer;
+};
+
+struct selector_statistics {
+	uint64_t n_pkts;
+};
+
 /*
  * Register array.
  */
@@ -873,6 +908,7 @@ struct thread {
 
 	/* Tables. */
 	struct table_runtime *tables;
+	struct selector_runtime *selectors;
 	struct rte_swx_table_state *table_state;
 	uint64_t action_id;
 	int hit; /* 0 = Miss, 1 = Hit. */
@@ -1308,6 +1344,7 @@ struct rte_swx_pipeline {
 	struct action_tailq actions;
 	struct table_type_tailq table_types;
 	struct table_tailq tables;
+	struct selector_tailq selectors;
 	struct regarray_tailq regarrays;
 	struct meter_profile_tailq meter_profiles;
 	struct metarray_tailq metarrays;
@@ -1317,6 +1354,7 @@ struct rte_swx_pipeline {
 	struct instruction **action_instructions;
 	struct rte_swx_table_state *table_state;
 	struct table_statistics *table_stats;
+	struct selector_statistics *selector_stats;
 	struct regarray_runtime *regarray_runtime;
 	struct metarray_runtime *metarray_runtime;
 	struct instruction *instructions;
@@ -1329,6 +1367,7 @@ struct rte_swx_pipeline {
 	uint32_t n_extern_funcs;
 	uint32_t n_actions;
 	uint32_t n_tables;
+	uint32_t n_selectors;
 	uint32_t n_regarrays;
 	uint32_t n_metarrays;
 	uint32_t n_headers;
@@ -3450,6 +3489,9 @@ instr_hdr_invalidate_exec(struct rte_swx_pipeline *p)
 static struct table *
 table_find(struct rte_swx_pipeline *p, const char *name);
 
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name);
+
 static int
 instr_table_translate(struct rte_swx_pipeline *p,
 		      struct action *action,
@@ -3459,16 +3501,26 @@ instr_table_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct table *t;
+	struct selector *s;
 
 	CHECK(!action, EINVAL);
 	CHECK(n_tokens == 2, EINVAL);
 
 	t = table_find(p, tokens[1]);
-	CHECK(t, EINVAL);
+	if (t) {
+		instr->type = INSTR_TABLE;
+		instr->table.table_id = t->id;
+		return 0;
+	}
 
-	instr->type = INSTR_TABLE;
-	instr->table.table_id = t->id;
-	return 0;
+	s = selector_find(p, tokens[1]);
+	if (s) {
+		instr->type = INSTR_SELECTOR;
+		instr->table.table_id = s->id;
+		return 0;
+	}
+
+	CHECK(0, EINVAL);
 }
 
 static inline void
@@ -3522,6 +3574,45 @@ instr_table_exec(struct rte_swx_pipeline *p)
 	thread_ip_action_call(p, t, action_id);
 }
 
+static inline void
+instr_selector_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+	uint32_t selector_id = ip->table.table_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables + selector_id];
+	struct selector_runtime *selector = &t->selectors[selector_id];
+	struct selector_statistics *stats = &p->selector_stats[selector_id];
+	uint64_t n_pkts = stats->n_pkts;
+	int done;
+
+	/* Table. */
+	done = rte_swx_table_selector_select(ts->obj,
+			   selector->mailbox,
+			   selector->group_id_buffer,
+			   selector->selector_buffer,
+			   selector->member_id_buffer);
+	if (!done) {
+		/* Thread. */
+		TRACE("[Thread %2u] selector %u (not finalized)\n",
+		      p->thread_id,
+		      selector_id);
+
+		thread_yield(p);
+		return;
+	}
+
+
+	TRACE("[Thread %2u] selector %u\n",
+	      p->thread_id,
+	      selector_id);
+
+	stats->n_pkts = n_pkts + 1;
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * extern.
  */
@@ -8787,6 +8878,7 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_METER_IMI] = instr_meter_imi_exec,
 
 	[INSTR_TABLE] = instr_table_exec,
+	[INSTR_SELECTOR] = instr_selector_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
 
@@ -9253,6 +9345,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 	CHECK_NAME(name, EINVAL);
 	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
 
 	CHECK(params, EINVAL);
 
@@ -9455,82 +9548,6 @@ table_params_free(struct rte_swx_table_params *params)
 	free(params);
 }
 
-static int
-table_state_build(struct rte_swx_pipeline *p)
-{
-	struct table *table;
-
-	p->table_state = calloc(p->n_tables,
-				sizeof(struct rte_swx_table_state));
-	CHECK(p->table_state, ENOMEM);
-
-	TAILQ_FOREACH(table, &p->tables, node) {
-		struct rte_swx_table_state *ts = &p->table_state[table->id];
-
-		if (table->type) {
-			struct rte_swx_table_params *params;
-
-			/* ts->obj. */
-			params = table_params_get(table);
-			CHECK(params, ENOMEM);
-
-			ts->obj = table->type->ops.create(params,
-				NULL,
-				table->args,
-				p->numa_node);
-
-			table_params_free(params);
-			CHECK(ts->obj, ENODEV);
-		}
-
-		/* ts->default_action_data. */
-		if (table->action_data_size_max) {
-			ts->default_action_data =
-				malloc(table->action_data_size_max);
-			CHECK(ts->default_action_data, ENOMEM);
-
-			memcpy(ts->default_action_data,
-			       table->default_action_data,
-			       table->action_data_size_max);
-		}
-
-		/* ts->default_action_id. */
-		ts->default_action_id = table->default_action->id;
-	}
-
-	return 0;
-}
-
-static void
-table_state_build_free(struct rte_swx_pipeline *p)
-{
-	uint32_t i;
-
-	if (!p->table_state)
-		return;
-
-	for (i = 0; i < p->n_tables; i++) {
-		struct rte_swx_table_state *ts = &p->table_state[i];
-		struct table *table = table_find_by_id(p, i);
-
-		/* ts->obj. */
-		if (table->type && ts->obj)
-			table->type->ops.free(ts->obj);
-
-		/* ts->default_action_data. */
-		free(ts->default_action_data);
-	}
-
-	free(p->table_state);
-	p->table_state = NULL;
-}
-
-static void
-table_state_free(struct rte_swx_pipeline *p)
-{
-	table_state_build_free(p);
-}
-
 static int
 table_stub_lkp(void *table __rte_unused,
 	       void *mailbox __rte_unused,
@@ -9658,6 +9675,458 @@ table_free(struct rte_swx_pipeline *p)
 	}
 }
 
+/*
+ * Selector.
+ */
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name)
+{
+	struct selector *s;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (strcmp(s->name, name) == 0)
+			return s;
+
+	return NULL;
+}
+
+static struct selector *
+selector_find_by_id(struct rte_swx_pipeline *p, uint32_t id)
+{
+	struct selector *s = NULL;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (s->id == id)
+			return s;
+
+	return NULL;
+}
+
+static int
+selector_fields_check(struct rte_swx_pipeline *p,
+		      struct rte_swx_pipeline_selector_params *params,
+		      struct header **header)
+{
+	struct header *h0 = NULL;
+	struct field *hf, *mf;
+	uint32_t i;
+
+	/* Return if no selector fields. */
+	if (!params->n_selector_fields || !params->selector_field_names)
+		return -EINVAL;
+
+	/* Check that all the selector fields either belong to the same header
+	 * or are all meta-data fields.
+	 */
+	hf = header_field_parse(p, params->selector_field_names[0], &h0);
+	mf = metadata_field_parse(p, params->selector_field_names[0]);
+	if (!hf && !mf)
+		return -EINVAL;
+
+	for (i = 1; i < params->n_selector_fields; i++)
+		if (h0) {
+			struct header *h;
+
+			hf = header_field_parse(p, params->selector_field_names[i], &h);
+			if (!hf || (h->id != h0->id))
+				return -EINVAL;
+		} else {
+			mf = metadata_field_parse(p, params->selector_field_names[i]);
+			if (!mf)
+				return -EINVAL;
+		}
+
+	/* Check that there are no duplicated match fields. */
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+		uint32_t j;
+
+		for (j = i + 1; j < params->n_selector_fields; j++)
+			if (!strcmp(params->selector_field_names[j], field_name))
+				return -EINVAL;
+	}
+
+	/* Return. */
+	if (header)
+		*header = h0;
+
+	return 0;
+}
+
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params)
+{
+	struct selector *s;
+	struct header *selector_header = NULL;
+	struct field *group_id_field, *member_id_field;
+	uint32_t i;
+	int status = 0;
+
+	CHECK(p, EINVAL);
+
+	CHECK_NAME(name, EINVAL);
+	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
+
+	CHECK(params, EINVAL);
+
+	CHECK_NAME(params->group_id_field_name, EINVAL);
+	group_id_field = metadata_field_parse(p, params->group_id_field_name);
+	CHECK(group_id_field, EINVAL);
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		CHECK_NAME(field_name, EINVAL);
+	}
+	status = selector_fields_check(p, params, &selector_header);
+	if (status)
+		return status;
+
+	CHECK_NAME(params->member_id_field_name, EINVAL);
+	member_id_field = metadata_field_parse(p, params->member_id_field_name);
+	CHECK(member_id_field, EINVAL);
+
+	CHECK(params->n_groups_max, EINVAL);
+
+	CHECK(params->n_members_per_group_max, EINVAL);
+
+	/* Memory allocation. */
+	s = calloc(1, sizeof(struct selector));
+	if (!s) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->selector_fields = calloc(params->n_selector_fields, sizeof(struct field *));
+	if (!s->selector_fields) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Node initialization. */
+	strcpy(s->name, name);
+
+	s->group_id_field = group_id_field;
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		s->selector_fields[i] = selector_header ?
+			header_field_parse(p, field_name, NULL) :
+			metadata_field_parse(p, field_name);
+	}
+
+	s->n_selector_fields = params->n_selector_fields;
+
+	s->selector_header = selector_header;
+
+	s->member_id_field = member_id_field;
+
+	s->n_groups_max = params->n_groups_max;
+
+	s->n_members_per_group_max = params->n_members_per_group_max;
+
+	s->id = p->n_selectors;
+
+	/* Node add to tailq. */
+	TAILQ_INSERT_TAIL(&p->selectors, s, node);
+	p->n_selectors++;
+
+	return 0;
+
+error:
+	if (!s)
+		return status;
+
+	free(s->selector_fields);
+
+	free(s);
+
+	return status;
+}
+
+static void
+selector_params_free(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return;
+
+	free(params->selector_mask);
+
+	free(params);
+}
+
+static struct rte_swx_table_selector_params *
+selector_table_params_get(struct selector *s)
+{
+	struct rte_swx_table_selector_params *params = NULL;
+	struct field *first, *last;
+	uint32_t i;
+
+	/* Memory allocation. */
+	params = calloc(1, sizeof(struct rte_swx_pipeline_selector_params));
+	if (!params)
+		goto error;
+
+	/* Group ID. */
+	params->group_id_offset = s->group_id_field->offset / 8;
+
+	/* Find first (smallest offset) and last (biggest offset) selector fields. */
+	first = s->selector_fields[0];
+	last = s->selector_fields[0];
+
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* Selector offset and size. */
+	params->selector_offset = first->offset / 8;
+	params->selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* Memory allocation. */
+	params->selector_mask = calloc(1, params->selector_size);
+	if (!params->selector_mask)
+		goto error;
+
+	/* Selector mask. */
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+		uint32_t start = (f->offset - first->offset) / 8;
+		size_t size = f->n_bits / 8;
+
+		memset(&params->selector_mask[start], 0xFF, size);
+	}
+
+	/* Member ID. */
+	params->member_id_offset = s->member_id_field->offset / 8;
+
+	/* Maximum number of groups. */
+	params->n_groups_max = s->n_groups_max;
+
+	/* Maximum number of members per group. */
+	params->n_members_per_group_max = s->n_members_per_group_max;
+
+	return params;
+
+error:
+	selector_params_free(params);
+	return NULL;
+}
+
+static void
+selector_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		uint32_t j;
+
+		if (!t->selectors)
+			continue;
+
+		for (j = 0; j < p->n_selectors; j++) {
+			struct selector_runtime *r = &t->selectors[j];
+
+			free(r->mailbox);
+		}
+
+		free(t->selectors);
+		t->selectors = NULL;
+	}
+
+	free(p->selector_stats);
+	p->selector_stats = NULL;
+}
+
+static int
+selector_build(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+	int status = 0;
+
+	/* Per pipeline: selector statistics. */
+	p->selector_stats = calloc(p->n_selectors, sizeof(struct selector_statistics));
+	if (!p->selector_stats) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Per thread: selector run-time. */
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		struct selector *s;
+
+		t->selectors = calloc(p->n_selectors, sizeof(struct selector_runtime));
+		if (!t->selectors) {
+			status = -ENOMEM;
+			goto error;
+		}
+
+		TAILQ_FOREACH(s, &p->selectors, node) {
+			struct selector_runtime *r = &t->selectors[s->id];
+			uint64_t size;
+
+			/* r->mailbox. */
+			size = rte_swx_table_selector_mailbox_size_get();
+			if (size) {
+				r->mailbox = calloc(1, size);
+				if (!r->mailbox) {
+					status = -ENOMEM;
+					goto error;
+				}
+			}
+
+			/* r->group_id_buffer. */
+			r->group_id_buffer = &t->structs[p->metadata_struct_id];
+
+			/* r->selector_buffer. */
+			r->selector_buffer = s->selector_header ?
+				&t->structs[s->selector_header->struct_id] :
+				&t->structs[p->metadata_struct_id];
+
+			/* r->member_id_buffer. */
+			r->member_id_buffer = &t->structs[p->metadata_struct_id];
+		}
+	}
+
+	return 0;
+
+error:
+	selector_build_free(p);
+	return status;
+}
+
+static void
+selector_free(struct rte_swx_pipeline *p)
+{
+	selector_build_free(p);
+
+	/* Selector tables. */
+	for ( ; ; ) {
+		struct selector *elem;
+
+		elem = TAILQ_FIRST(&p->selectors);
+		if (!elem)
+			break;
+
+		TAILQ_REMOVE(&p->selectors, elem, node);
+		free(elem->selector_fields);
+		free(elem);
+	}
+}
+
+/*
+ * Table state.
+ */
+static int
+table_state_build(struct rte_swx_pipeline *p)
+{
+	struct table *table;
+	struct selector *s;
+
+	p->table_state = calloc(p->n_tables + p->n_selectors,
+				sizeof(struct rte_swx_table_state));
+	CHECK(p->table_state, ENOMEM);
+
+	TAILQ_FOREACH(table, &p->tables, node) {
+		struct rte_swx_table_state *ts = &p->table_state[table->id];
+
+		if (table->type) {
+			struct rte_swx_table_params *params;
+
+			/* ts->obj. */
+			params = table_params_get(table);
+			CHECK(params, ENOMEM);
+
+			ts->obj = table->type->ops.create(params,
+				NULL,
+				table->args,
+				p->numa_node);
+
+			table_params_free(params);
+			CHECK(ts->obj, ENODEV);
+		}
+
+		/* ts->default_action_data. */
+		if (table->action_data_size_max) {
+			ts->default_action_data =
+				malloc(table->action_data_size_max);
+			CHECK(ts->default_action_data, ENOMEM);
+
+			memcpy(ts->default_action_data,
+			       table->default_action_data,
+			       table->action_data_size_max);
+		}
+
+		/* ts->default_action_id. */
+		ts->default_action_id = table->default_action->id;
+	}
+
+	TAILQ_FOREACH(s, &p->selectors, node) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
+		struct rte_swx_table_selector_params *params;
+
+		/* ts->obj. */
+		params = selector_table_params_get(s);
+		CHECK(params, ENOMEM);
+
+		ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+
+		selector_params_free(params);
+		CHECK(ts->obj, ENODEV);
+	}
+
+	return 0;
+}
+
+static void
+table_state_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	if (!p->table_state)
+		return;
+
+	for (i = 0; i < p->n_tables; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[i];
+		struct table *table = table_find_by_id(p, i);
+
+		/* ts->obj. */
+		if (table->type && ts->obj)
+			table->type->ops.free(ts->obj);
+
+		/* ts->default_action_data. */
+		free(ts->default_action_data);
+	}
+
+	for (i = 0; i < p->n_selectors; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];
+
+		/* ts->obj. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
+	free(p->table_state);
+	p->table_state = NULL;
+}
+
+static void
+table_state_free(struct rte_swx_pipeline *p)
+{
+	table_state_build_free(p);
+}
+
 /*
  * Register array.
  */
@@ -9988,6 +10457,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)
 	TAILQ_INIT(&pipeline->actions);
 	TAILQ_INIT(&pipeline->table_types);
 	TAILQ_INIT(&pipeline->tables);
+	TAILQ_INIT(&pipeline->selectors);
 	TAILQ_INIT(&pipeline->regarrays);
 	TAILQ_INIT(&pipeline->meter_profiles);
 	TAILQ_INIT(&pipeline->metarrays);
@@ -10010,6 +10480,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)
 	metarray_free(p);
 	regarray_free(p);
 	table_state_free(p);
+	selector_free(p);
 	table_free(p);
 	action_free(p);
 	metadata_free(p);
@@ -10089,6 +10560,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	if (status)
 		goto error;
 
+	status = selector_build(p);
+	if (status)
+		goto error;
+
 	status = table_state_build(p);
 	if (status)
 		goto error;
@@ -10108,6 +10583,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	metarray_build_free(p);
 	regarray_build_free(p);
 	table_state_build_free(p);
+	selector_build_free(p);
 	table_build_free(p);
 	action_build_free(p);
 	metadata_build_free(p);
@@ -10167,6 +10643,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,
 	pipeline->n_ports_out = p->n_ports_out;
 	pipeline->n_actions = n_actions;
 	pipeline->n_tables = n_tables;
+	pipeline->n_selectors = p->n_selectors;
 	pipeline->n_regarrays = p->n_regarrays;
 	pipeline->n_metarrays = p->n_metarrays;
 
@@ -10320,6 +10797,98 @@ rte_swx_ctl_table_ops_get(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector)
+{
+	struct selector *s = NULL;
+
+	if (!p || !selector)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	strcpy(selector->name, s->name);
+
+	selector->n_selector_fields = s->n_selector_fields;
+	selector->n_groups_max = s->n_groups_max;
+	selector->n_members_per_group_max = s->n_members_per_group_max;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->group_id_field->n_bits;
+	field->offset = s->group_id_field->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 uint32_t selector_field_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+	struct field *f;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s || (selector_field_id >= s->n_selector_fields))
+		return -EINVAL;
+
+	f = s->selector_fields[selector_field_id];
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = s->selector_header ? 1 : 0;
+	field->n_bits = f->n_bits;
+	field->offset = f->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->member_id_field->n_bits;
+	field->offset = s->member_id_field->offset;
+
+	return 0;
+}
+
 int
 rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
 				 struct rte_swx_table_state **table_state)
@@ -10405,6 +10974,25 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+	const char *selector_name,
+	struct rte_swx_pipeline_selector_stats *stats)
+{
+	struct selector *s;
+
+	if (!p || !selector_name || !selector_name[0] || !stats)
+		return -EINVAL;
+
+	s = selector_find(p, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	stats->n_pkts = p->selector_stats[s->id].n_pkts;
+
+	return 0;
+}
+
 int
 rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,
 			      uint32_t regarray_id,
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index feeb10a5c..cd395ac39 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -612,6 +612,57 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 			      const char *args,
 			      uint32_t size);
 
+/** Pipeline selector table parameters. */
+struct rte_swx_pipeline_selector_params {
+	/** The group ID field. Input into the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *group_id_field_name;
+
+	/** The set of fields used to select (through a hashing scheme) the
+	 * member within the current group. Inputs into the seletion operation.
+	 * Restriction: All the selector fields must be part of the same struct,
+	 * i.e. part of the same header or part of the meta-data structure.
+	 */
+	const char **selector_field_names;
+
+	/** The number of selector fields. Must be non-zero. */
+	uint32_t n_selector_fields;
+
+	/** The member ID field. Output from the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *member_id_field_name;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Pipeline selector table configure
+ *
+ * @param[out] p
+ *   Pipeline handle.
+ * @param[in] name
+ *   Selector table name.
+ * @param[in] params
+ *   Selector table parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough space/cannot allocate memory;
+ *   -EEXIST: Selector table with this name already exists;
+ *   -ENODEV: Selector table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params);
+
 /**
  * Pipeline register array configure
  *
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index 2e867d7bf..6980b0390 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -18,7 +18,9 @@
 #define TABLE_BLOCK 2
 #define TABLE_KEY_BLOCK 3
 #define TABLE_ACTIONS_BLOCK 4
-#define APPLY_BLOCK 5
+#define SELECTOR_BLOCK 5
+#define SELECTOR_SELECTOR_BLOCK 6
+#define APPLY_BLOCK 7
 
 /*
  * extobj.
@@ -940,6 +942,307 @@ table_block_parse(struct table_spec *s,
 	return -EINVAL;
 }
 
+/*
+ * selector.
+ *
+ * selector SELECTOR_NAME {
+ *	group_id FIELD_NAME
+ *	selector {
+ *		FIELD_NAME
+ *		...
+ *	}
+ *	member_id FIELD_NAME
+ *	n_groups N_GROUPS
+ *	n_members_per_group N_MEMBERS_PER_GROUP
+ * }
+ */
+struct selector_spec {
+	char *name;
+	struct rte_swx_pipeline_selector_params params;
+};
+
+static void
+selector_spec_free(struct selector_spec *s)
+{
+	uintptr_t field_name;
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	/* name. */
+	free(s->name);
+	s->name = NULL;
+
+	/* params->group_id_field_name. */
+	field_name = (uintptr_t)s->params.group_id_field_name;
+	free((void *)field_name);
+	s->params.group_id_field_name = NULL;
+
+	/* params->selector_field_names. */
+	for (i = 0; i < s->params.n_selector_fields; i++) {
+		field_name = (uintptr_t)s->params.selector_field_names[i];
+
+		free((void *)field_name);
+	}
+
+	free(s->params.selector_field_names);
+	s->params.selector_field_names = NULL;
+
+	s->params.n_selector_fields = 0;
+
+	/* params->member_id_field_name. */
+	field_name = (uintptr_t)s->params.member_id_field_name;
+	free((void *)field_name);
+	s->params.member_id_field_name = NULL;
+
+	/* params->n_groups_max. */
+	s->params.n_groups_max = 0;
+
+	/* params->n_members_per_group_max. */
+	s->params.n_members_per_group_max = 0;
+}
+
+static int
+selector_statement_parse(struct selector_spec *s,
+			 uint32_t *block_mask,
+			 char **tokens,
+			 uint32_t n_tokens,
+			 uint32_t n_lines,
+			 uint32_t *err_line,
+			 const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 3) || strcmp(tokens[2], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	if (!s->name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_statement_parse(uint32_t *block_mask,
+				  char **tokens,
+				  uint32_t n_tokens,
+				  uint32_t n_lines,
+				  uint32_t *err_line,
+				  const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_block_parse(struct selector_spec *s,
+			      uint32_t *block_mask,
+			      char **tokens,
+			      uint32_t n_tokens,
+			      uint32_t n_lines,
+			      uint32_t *err_line,
+			      const char **err_msg)
+{
+	const char **new_fields;
+	char *name;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_SELECTOR_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector field statement.";
+		return -EINVAL;
+	}
+
+	name = strdup(tokens[0]);
+	if (!name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_fields = realloc(s->params.selector_field_names,
+			     (s->params.n_selector_fields + 1) * sizeof(char *));
+	if (!new_fields) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.selector_field_names = new_fields;
+	s->params.selector_field_names[s->params.n_selector_fields] = name;
+	s->params.n_selector_fields++;
+
+	return 0;
+}
+
+static int
+selector_block_parse(struct selector_spec *s,
+		     uint32_t *block_mask,
+		     char **tokens,
+		     uint32_t n_tokens,
+		     uint32_t n_lines,
+		     uint32_t *err_line,
+		     const char **err_msg)
+{
+	if (*block_mask & (1 << SELECTOR_SELECTOR_BLOCK))
+		return selector_selector_block_parse(s,
+						     block_mask,
+						     tokens,
+						     n_tokens,
+						     n_lines,
+						     err_line,
+						     err_msg);
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_BLOCK);
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "group_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid group_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.group_id_field_name = strdup(tokens[1]);
+		if (!s->params.group_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "selector"))
+		return selector_selector_statement_parse(block_mask,
+							 tokens,
+							 n_tokens,
+							 n_lines,
+							 err_line,
+							 err_msg);
+
+	if (!strcmp(tokens[0], "member_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid member_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.member_id_field_name = strdup(tokens[1]);
+		if (!s->params.member_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_groups_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_groups_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_members_per_group_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_members_per_group_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* Anything else. */
+	if (err_line)
+		*err_line = n_lines;
+	if (err_msg)
+		*err_msg = "Invalid statement.";
+	return -EINVAL;
+}
+
 /*
  * regarray.
  *
@@ -1203,6 +1506,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	struct metadata_spec metadata_spec = {0};
 	struct action_spec action_spec = {0};
 	struct table_spec table_spec = {0};
+	struct selector_spec selector_spec = {0};
 	struct regarray_spec regarray_spec = {0};
 	struct metarray_spec metarray_spec = {0};
 	struct apply_spec apply_spec = {0};
@@ -1386,6 +1690,38 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector block. */
+		if (block_mask & (1 << SELECTOR_BLOCK)) {
+			status = selector_block_parse(&selector_spec,
+						      &block_mask,
+						      tokens,
+						      n_tokens,
+						      n_lines,
+						      err_line,
+						      err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << SELECTOR_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_selector_config(p,
+				selector_spec.name,
+				&selector_spec.params);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Selector configuration error.";
+				goto error;
+			}
+
+			selector_spec_free(&selector_spec);
+
+			continue;
+		}
+
 		/* apply block. */
 		if (block_mask & (1 << APPLY_BLOCK)) {
 			status = apply_block_parse(&apply_spec,
@@ -1544,6 +1880,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector. */
+		if (!strcmp(tokens[0], "selector")) {
+			status = selector_statement_parse(&selector_spec,
+							  &block_mask,
+							  tokens,
+							  n_tokens,
+							  n_lines,
+							  err_line,
+							  err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
 		/* regarray. */
 		if (!strcmp(tokens[0], "regarray")) {
 			status = regarray_statement_parse(&regarray_spec,
@@ -1651,6 +2002,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	metadata_spec_free(&metadata_spec);
 	action_spec_free(&action_spec);
 	table_spec_free(&table_spec);
+	selector_spec_free(&selector_spec);
 	regarray_spec_free(&regarray_spec);
 	metarray_spec_free(&metarray_spec);
 	apply_spec_free(&apply_spec);
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index a4d7d9788..ff0974c2e 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -116,4 +116,17 @@ EXPERIMENTAL {
 	rte_swx_ctl_regarray_info_get;
 	rte_swx_pipeline_metarray_config;
 	rte_swx_pipeline_regarray_config;
+
+	#added in 21.08
+	rte_swx_pipeline_selector_config;
+	rte_swx_ctl_pipeline_selector_fprintf;
+	rte_swx_ctl_pipeline_selector_group_add;
+	rte_swx_ctl_pipeline_selector_group_delete;
+	rte_swx_ctl_pipeline_selector_group_member_add;
+	rte_swx_ctl_pipeline_selector_group_member_delete;
+	rte_swx_ctl_pipeline_selector_stats_read;
+	rte_swx_ctl_selector_info_get;
+	rte_swx_ctl_selector_field_info_get;
+	rte_swx_ctl_selector_group_id_field_info_get;
+	rte_swx_ctl_selector_member_id_field_info_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V2 4/5] examples/pipeline: add support for selector tables
  2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
                   ` (2 preceding siblings ...)
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 3/5] pipeline: " Cristian Dumitrescu
@ 2021-07-02 22:40 ` Cristian Dumitrescu
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 5/5] examples/pipeline: add selector example Cristian Dumitrescu
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:40 UTC (permalink / raw)
  To: dev; +Cc: Churchill Khangar

Add application-evel support for selector tables.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 563 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 563 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 30754e319..f67783c8f 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1368,6 +1368,467 @@ cmd_pipeline_table_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
 }
 
+static const char cmd_pipeline_selector_group_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group add\n";
+
+static void
+cmd_pipeline_selector_group_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group add");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_add(p->ctl,
+		selector_name,
+		&group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+	else
+		snprintf(out, out_size, "Group ID: %u\n", group_id);
+}
+
+static const char cmd_pipeline_selector_group_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group delete <group_id>\n";
+
+static void
+cmd_pipeline_selector_group_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 7) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group delete");
+		return;
+	}
+
+	if (parser_read_uint32(&group_id, tokens[6]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "group_id");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_delete(p->ctl,
+		selector_name,
+		group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+}
+
+#define GROUP_MEMBER_INFO_TOKENS_MAX 6
+
+static int
+token_is_comment(const char *token)
+{
+	if ((token[0] == '#') ||
+	    (token[0] == ';') ||
+	    ((token[0] == '/') && (token[1] == '/')))
+		return 1; /* TRUE. */
+
+	return 0; /* FALSE. */
+}
+
+static int
+pipeline_selector_group_member_read(const char *string,
+				      uint32_t *group_id,
+				      uint32_t *member_id,
+				      uint32_t *weight,
+				      int *is_blank_or_comment)
+{
+	char *token_array[GROUP_MEMBER_INFO_TOKENS_MAX], **tokens;
+	char *s0 = NULL, *s;
+	uint32_t n_tokens = 0, group_id_val, member_id_val, weight_val;
+	int blank_or_comment = 0;
+
+	/* Check input arguments. */
+	if (!string || !string[0])
+		goto error;
+
+	/* Memory allocation. */
+	s0 = strdup(string);
+	if (!s0)
+		goto error;
+
+	/* Parse the string into tokens. */
+	for (s = s0; ; ) {
+		char *token;
+
+		token = strtok_r(s, " \f\n\r\t\v", &s);
+		if (!token || token_is_comment(token))
+			break;
+
+		if (n_tokens > GROUP_MEMBER_INFO_TOKENS_MAX)
+			goto error;
+
+		token_array[n_tokens] = token;
+		n_tokens++;
+	}
+
+	if (!n_tokens) {
+		blank_or_comment = 1;
+		goto error;
+	}
+
+	tokens = token_array;
+
+	if (n_tokens < 4 ||
+		strcmp(tokens[0], "group") ||
+		strcmp(tokens[2], "member"))
+		goto error;
+
+	/*
+	 * Group ID.
+	 */
+	if (parser_read_uint32(&group_id_val, tokens[1]) != 0)
+		goto error;
+	*group_id = group_id_val;
+
+	/*
+	 * Member ID.
+	 */
+	if (parser_read_uint32(&member_id_val, tokens[3]) != 0)
+		goto error;
+	*member_id = member_id_val;
+
+	tokens += 4;
+	n_tokens -= 4;
+
+	/*
+	 * Weight.
+	 */
+	if (n_tokens && !strcmp(tokens[0], "weight")) {
+		if (n_tokens < 2)
+			goto error;
+
+		if (parser_read_uint32(&weight_val, tokens[1]) != 0)
+			goto error;
+		*weight = weight_val;
+
+		tokens += 2;
+		n_tokens -= 2;
+	}
+
+	if (n_tokens)
+		goto error;
+
+	free(s0);
+	return 0;
+
+error:
+	free(s0);
+	if (is_blank_or_comment)
+		*is_blank_or_comment = blank_or_comment;
+	return -EINVAL;
+}
+
+static int
+pipeline_selector_group_members_add(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_add(p,
+			selector_name,
+			group_id,
+			member_id,
+			weight);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member add <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member add");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_add(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_selector_group_members_delete(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_delete(p,
+			selector_name,
+			group_id,
+			member_id);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member delete <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member delete");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_delete(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static const char cmd_pipeline_selector_show_help[] =
+"pipeline <pipeline_name> selector <selector_name> show\n";
+
+static void
+cmd_pipeline_selector_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	int status;
+
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	selector_name = tokens[3];
+	status = rte_swx_ctl_pipeline_selector_fprintf(stdout,
+		p->ctl, selector_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2168,6 +2629,11 @@ cmd_help(char **tokens,
 			"\tpipeline table delete\n"
 			"\tpipeline table default\n"
 			"\tpipeline table show\n"
+			"\tpipeline selector group add\n"
+			"\tpipeline selector group delete\n"
+			"\tpipeline selector group member add\n"
+			"\tpipeline selector group member delete\n"
+			"\tpipeline selector show\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2266,6 +2732,57 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_show_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -2468,6 +2985,52 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "add") == 0)) {
+			cmd_pipeline_selector_group_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "delete") == 0)) {
+			cmd_pipeline_selector_group_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "add") == 0)) {
+			cmd_pipeline_selector_group_member_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "delete") == 0)) {
+			cmd_pipeline_selector_group_member_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_selector_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V2 5/5] examples/pipeline: add selector example
  2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
                   ` (3 preceding siblings ...)
  2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 4/5] examples/pipeline: " Cristian Dumitrescu
@ 2021-07-02 22:40 ` Cristian Dumitrescu
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:40 UTC (permalink / raw)
  To: dev

Added the files to illustrate the selector table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/examples/selector.cli  | 31 ++++++++
 examples/pipeline/examples/selector.spec | 95 ++++++++++++++++++++++++
 examples/pipeline/examples/selector.txt  |  4 +
 3 files changed, 130 insertions(+)
 create mode 100644 examples/pipeline/examples/selector.cli
 create mode 100644 examples/pipeline/examples/selector.spec
 create mode 100644 examples/pipeline/examples/selector.txt

diff --git a/examples/pipeline/examples/selector.cli b/examples/pipeline/examples/selector.cli
new file mode 100644
index 000000000..36f3ead54
--- /dev/null
+++ b/examples/pipeline/examples/selector.cli
@@ -0,0 +1,31 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/selector.spec
+
+pipeline PIPELINE0 selector s group add
+pipeline PIPELINE0 selector s group member add ./examples/pipeline/examples/selector.txt
+pipeline PIPELINE0 commit
+pipeline PIPELINE0 selector s show
+
+thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/selector.spec b/examples/pipeline/examples/selector.spec
new file mode 100644
index 000000000..72e49cb1d
--- /dev/null
+++ b/examples/pipeline/examples/selector.spec
@@ -0,0 +1,95 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; A selector table is made out of groups of weighted members, with a given member potentially part
+; of several groups. The select operation returns a member ID by first selecting a group based on an
+; input group ID and then selecting a member within that group by hashing one or several input
+; header or meta-data fields. It is very useful for implementing an Equal-Cost Multi-Path (ECMP) or
+; Weighted-Cost Multi-Path (WCMP) enabled FIB or a load balancer. It is part of the action selector
+; construct described by the P4 Portable Switch Architecture (PSA) specification.
+;
+; Normally, an action selector FIB is built with a routing table (the base table), a selector table
+; (the group table) and a next hop table (the member table). One of the routing table actions sets
+; up the group ID meta-data field used as the index into the group table, which produces the member
+; ID meta-data field, i.e. the next hop ID that is used as the index into the next hop table. The
+; next hop action prepares the output packet for being sent next hop in the network by prepending
+; one or several headers to the packet (Ethernet at the very least), decrementing the TTL and
+; recomputing the IPv4 checksum, etc. The selector allows for multiple next hops to be specified
+; for any given route as opposed to a single next hop per route; for every packet, its next hop is
+; picked out of the set of next hops defined for the route while preserving the packet ordering
+; within the flow, with the flow defined by the selector n-tuple fields.
+;
+; In this simple example, the base table and the member table are striped out in order to focus
+; exclusively on illustrating the selector table. The group_id is read from the destination MAC
+; address and the selector n-tuple is represented by the Protocol, the source IP address and the
+; destination IP address fields. The member_id produced by the selector table is used to identify
+; the output port which facilitates the testing of different member weights by simply comparing the
+; rates of output packets sent on different ports.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+	bit<32> group_id
+}
+
+metadata instanceof metadata_t
+
+//
+// Selectors.
+//
+selector s {
+	group_id m.group_id
+
+	selector {
+		h.ipv4.protocol
+		h.ipv4.src_addr
+		h.ipv4.dst_addr
+	}
+
+	member_id m.port_out
+
+	n_groups_max 64
+	n_members_per_group_max 16
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	mov m.group_id h.ethernet.dst_addr
+	table s
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
diff --git a/examples/pipeline/examples/selector.txt b/examples/pipeline/examples/selector.txt
new file mode 100644
index 000000000..b3c83c773
--- /dev/null
+++ b/examples/pipeline/examples/selector.txt
@@ -0,0 +1,4 @@
+group 0 member 0 weight 1
+group 0 member 1 weight 1
+group 0 member 2 weight 2
+group 0 member 3 weight 4
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands
  2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 " Cristian Dumitrescu
@ 2021-07-02 22:46   ` Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 2/5] table: add support for selector tables Cristian Dumitrescu
                       ` (4 more replies)
  0 siblings, 5 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:46 UTC (permalink / raw)
  To: dev; +Cc: Churchill Khangar

From: Churchill Khangar <churchill.khangar@intel.com>

For more felxibility, the single monolithic table update command is
split into table entry add, table entry delete, table default entry
add, pipeline commit and pipeline abort.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                   | 589 ++++++++++++++++------
 examples/pipeline/examples/vxlan.cli      |   3 +-
 examples/pipeline/examples/vxlan_pcap.cli |   3 +-
 3 files changed, 428 insertions(+), 167 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 215dd8e85..30754e319 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1038,25 +1038,76 @@ table_entry_free(struct rte_swx_table_entry *entry)
 	free(entry);
 }
 
-static const char cmd_pipeline_table_update_help[] =
-"pipeline <pipeline_name> table <table_name> update <file_name_add> "
-"<file_name_delete> <file_name_default>";
+#ifndef MAX_LINE_SIZE
+#define MAX_LINE_SIZE 2048
+#endif
+
+static int
+pipeline_table_entries_add(struct rte_swx_ctl_pipeline *p,
+			   const char *table_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_add(p,
+							      table_name,
+							      entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_table_add_help[] =
+"pipeline <pipeline_name> table <table_name> add <file_name>\n";
 
 static void
-cmd_pipeline_table_update(char **tokens,
-	uint32_t n_tokens,
-	char *out,
-	size_t out_size,
-	void *obj)
+cmd_pipeline_table_add(char **tokens,
+		       uint32_t n_tokens,
+		       char *out,
+		       size_t out_size,
+		       void *obj)
 {
 	struct pipeline *p;
-	char *pipeline_name, *table_name, *line = NULL;
-	char *file_name_add, *file_name_delete, *file_name_default;
-	FILE *file_add = NULL, *file_delete = NULL, *file_default = NULL;
-	uint32_t line_id;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
 	int status;
 
-	if (n_tokens != 8) {
+	if (n_tokens != 6) {
 		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
 		return;
 	}
@@ -1068,192 +1119,313 @@ cmd_pipeline_table_update(char **tokens,
 		return;
 	}
 
-	if (strcmp(tokens[2], "table") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "table");
+	table_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_table_entries_add(p->ctl,
+					    table_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_entries_delete(struct rte_swx_ctl_pipeline *p,
+			      const char *table_name,
+			      FILE *file,
+			      uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_delete(p,
+								 table_name,
+								 entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_delete_help[] =
+"pipeline <pipeline_name> table <table_name> delete <file_name>\n";
+
+static void
+cmd_pipeline_table_delete(char **tokens,
+			  uint32_t n_tokens,
+			  char *out,
+			  size_t out_size,
+			  void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
 	table_name = tokens[3];
 
-	if (strcmp(tokens[4], "update") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "update");
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
 		return;
 	}
 
-	file_name_add = tokens[5];
-	file_name_delete = tokens[6];
-	file_name_default = tokens[7];
+	status = pipeline_table_entries_delete(p->ctl,
+					       table_name,
+					       file,
+					       &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				 const char *table_name,
+				 FILE *file,
+				 uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
 
-	/* File open. */
-	if (strcmp(file_name_add, "none")) {
-		file_add = fopen(file_name_add, "r");
-		if (!file_add) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_add);
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
 			goto error;
 		}
-	}
 
-	if (strcmp(file_name_delete, "none")) {
-		file_delete = fopen(file_name_delete, "r");
-		if (!file_delete) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_delete);
+		status = rte_swx_ctl_pipeline_table_default_entry_add(p,
+								      table_name,
+								      entry);
+		table_entry_free(entry);
+		if (status)
 			goto error;
-		}
 	}
 
-	if (strcmp(file_name_default, "none")) {
-		file_default = fopen(file_name_default, "r");
-		if (!file_default) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_default);
-			goto error;
-		}
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_default_help[] =
+"pipeline <pipeline_name> table <table_name> default <file_name>\n";
+
+static void
+cmd_pipeline_table_default(char **tokens,
+			   uint32_t n_tokens,
+			   char *out,
+			   size_t out_size,
+			   void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
 	}
 
-	if (!file_add && !file_delete && !file_default) {
-		snprintf(out, out_size, "Nothing to be done.");
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
-	/* Buffer allocation. */
-	line = malloc(2048);
-	if (!line) {
-		snprintf(out, out_size, MSG_OUT_OF_MEMORY);
-		goto error;
-	}
-
-	/* Add. */
-	if (file_add)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
-
-			if (fgets(line, 2048, file_add) == NULL)
-				break;
-
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
-
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_add, line_id);
-				goto error;
-			}
+	table_name = tokens[3];
 
-			status = rte_swx_ctl_pipeline_table_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_add, line_id);
-				goto error;
-			}
-		}
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
 
+	status = pipeline_table_default_entry_add(p->ctl,
+						  table_name,
+						  file,
+						  &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
 
-	/* Delete. */
-	if (file_delete)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	fclose(file);
+}
 
-			if (fgets(line, 2048, file_delete) == NULL)
-				break;
+static const char cmd_pipeline_table_show_help[] =
+"pipeline <pipeline_name> table <table_name> show\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_table_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_delete, line_id);
-				goto error;
-			}
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_entry_delete(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status)  {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_delete, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-	/* Default. */
-	if (file_default)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	table_name = tokens[3];
+	status = rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
+}
 
-			if (fgets(line, 2048, file_default) == NULL)
-				break;
+static const char cmd_pipeline_commit_help[] =
+"pipeline <pipeline_name> commit\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_commit(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_default, line_id);
-				goto error;
-			}
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_default_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_default, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
 	status = rte_swx_ctl_pipeline_commit(p->ctl, 1);
-	if (status) {
-		snprintf(out, out_size, "Commit failed.");
-		goto error;
-	}
+	if (status)
+		snprintf(out, out_size, "Commit failed. "
+			"Use \"commit\" to retry or \"abort\" to discard the pending work.\n");
+}
 
+static const char cmd_pipeline_abort_help[] =
+"pipeline <pipeline_name> abort\n";
 
-	rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+static void
+cmd_pipeline_abort(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
 
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
-	return;
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-error:
 	rte_swx_ctl_pipeline_abort(p->ctl);
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
 }
 
 static const char cmd_pipeline_regrd_help[] =
@@ -1992,7 +2164,12 @@ cmd_help(char **tokens,
 			"\tpipeline port in\n"
 			"\tpipeline port out\n"
 			"\tpipeline build\n"
-			"\tpipeline table update\n"
+			"\tpipeline table add\n"
+			"\tpipeline table delete\n"
+			"\tpipeline table default\n"
+			"\tpipeline table show\n"
+			"\tpipeline commit\n"
+			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
 			"\tpipeline regwr\n"
 			"\tpipeline meter profile add\n"
@@ -2056,9 +2233,52 @@ cmd_help(char **tokens,
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 3) &&
 		(strcmp(tokens[1], "table") == 0) &&
-		(strcmp(tokens[2], "update") == 0)) {
+		(strcmp(tokens[2], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
 		snprintf(out, out_size, "\n%s\n",
-			cmd_pipeline_table_update_help);
+			cmd_pipeline_table_default_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_show_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "commit") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_commit_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "abort") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_abort_help);
 		return;
 	}
 
@@ -2216,9 +2436,48 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "add") == 0)) {
+			cmd_pipeline_table_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "delete") == 0)) {
+			cmd_pipeline_table_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_table_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_table_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 3) &&
+			(strcmp(tokens[2], "commit") == 0)) {
+			cmd_pipeline_commit(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
-			(strcmp(tokens[2], "table") == 0)) {
-			cmd_pipeline_table_update(tokens, n_tokens, out,
+			(strcmp(tokens[2], "abort") == 0)) {
+			cmd_pipeline_abort(tokens, n_tokens, out,
 				out_size, obj);
 			return;
 		}
diff --git a/examples/pipeline/examples/vxlan.cli b/examples/pipeline/examples/vxlan.cli
index 7bf4a5757..a3bde6a9f 100644
--- a/examples/pipeline/examples/vxlan.cli
+++ b/examples/pipeline/examples/vxlan.cli
@@ -22,6 +22,7 @@ pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/pipeline/examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/vxlan_pcap.cli b/examples/pipeline/examples/vxlan_pcap.cli
index 1636ba080..3cc9a94af 100644
--- a/examples/pipeline/examples/vxlan_pcap.cli
+++ b/examples/pipeline/examples/vxlan_pcap.cli
@@ -17,6 +17,7 @@ pipeline PIPELINE0 port out 3 sink none
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V3 2/5] table: add support for selector tables
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
@ 2021-07-02 22:46     ` Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 3/5] pipeline: " Cristian Dumitrescu
                       ` (3 subsequent siblings)
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:46 UTC (permalink / raw)
  To: dev

A selector table is made up of groups of weighted members, with a
given member potentially part of several groups. The select operation
returns a member ID by first selecting a group based on an input group
ID and then selecting a member within that group based on hashing one
or several input header/meta-data fields. It is very useful for
implementing an ECMP/WCMP-enabled FIB or a load balancer. It is part
of the action selector described by the P4 Portable Switch
Architecture (PSA) specification.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/table/meson.build              |   2 +
 lib/table/rte_swx_table_selector.c | 581 +++++++++++++++++++++++++++++
 lib/table/rte_swx_table_selector.h | 203 ++++++++++
 lib/table/version.map              |   8 +
 4 files changed, 794 insertions(+)
 create mode 100644 lib/table/rte_swx_table_selector.c
 create mode 100644 lib/table/rte_swx_table_selector.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index b7b70b805..16e55f086 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+	'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
         'rte_table_array.c',
@@ -20,6 +21,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+	'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
         'rte_table_acl.h',
diff --git a/lib/table/rte_swx_table_selector.c b/lib/table/rte_swx_table_selector.c
new file mode 100644
index 000000000..541ebc221
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.c
@@ -0,0 +1,581 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_selector.h"
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+struct group_member_info {
+	uint32_t member_id;
+	uint32_t member_weight;
+	uint32_t member_weight_normalized;
+	uint32_t count;
+};
+
+struct table {
+	/* Input parameters */
+	struct rte_swx_table_selector_params params;
+
+	/* Internal. */
+	uint32_t *group_table;
+	uint64_t group_table_size;
+	struct group_member_info *members;
+	uint32_t n_members_per_group_max_log2;
+};
+
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max)
+{
+	uint64_t group_table_size, members_size;
+
+	group_table_size = n_groups_max * n_members_per_group_max * sizeof(uint32_t);
+
+	members_size = n_members_per_group_max * sizeof(struct group_member_info);
+
+	return sizeof(struct table) + group_table_size + members_size;
+}
+
+void
+rte_swx_table_selector_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	free(t->members);
+
+	env_free(t->group_table, t->group_table_size);
+
+	free(t->params.selector_mask);
+
+	free(t);
+}
+
+static int
+table_create_check(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return -1;
+
+	if (!params->selector_size ||
+	    (params->selector_size > 64) ||
+	    !params->n_groups_max ||
+	    (params->n_groups_max > 1U << 31) ||
+	    !params->n_members_per_group_max ||
+	    (params->n_members_per_group_max > 1U << 31))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+table_params_copy(struct table *t, struct rte_swx_table_selector_params *params)
+{
+	uint32_t selector_size, i;
+
+	selector_size = rte_align32pow2(params->selector_size);
+	if (selector_size < 8)
+		selector_size = 8;
+
+	memcpy(&t->params, params, sizeof(struct rte_swx_table_selector_params));
+	t->params.selector_size = selector_size;
+	t->params.selector_mask = NULL;
+	t->params.n_groups_max = rte_align32pow2(params->n_groups_max);
+	t->params.n_members_per_group_max = rte_align32pow2(params->n_members_per_group_max);
+
+	for (i = 0; i < 32; i++)
+		if (params->n_members_per_group_max == 1U << i)
+			t->n_members_per_group_max_log2 = i;
+
+	/* t->params.selector_mask */
+	t->params.selector_mask = calloc(selector_size, sizeof(uint8_t));
+	if (!t->params.selector_mask)
+		goto error;
+
+	if (params->selector_mask)
+		memcpy(t->params.selector_mask, params->selector_mask, params->selector_size);
+	else
+		memset(t->params.selector_mask, 0xFF, params->selector_size);
+
+	return 0;
+
+error:
+	free(t->params.selector_mask);
+	t->params.selector_mask = NULL;
+
+	return -ENOMEM;
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group);
+
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node)
+{
+	struct table *t = NULL;
+	uint32_t group_size, i;
+	int status;
+
+	/* Check input arguments. */
+	status = table_create_check(params);
+	if (status)
+		goto error;
+
+	/* Table object. */
+	t = calloc(1, sizeof(struct table));
+	if (!t)
+		goto error;
+
+	/* Parameter copy. */
+	status = table_params_copy(t, params);
+	if (status)
+		goto error;
+
+	/* Group. */
+	group_size = params->n_members_per_group_max * sizeof(uint32_t);
+	t->group_table_size = params->n_groups_max * group_size;
+
+	t->group_table = env_calloc(t->group_table_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t->group_table)
+		goto error;
+
+	t->members = calloc(params->n_members_per_group_max, sizeof(struct group_member_info));
+	if (!t->members)
+		goto error;
+
+	if (groups)
+		for (i = 0; i < params->n_groups_max; i++)
+			if (groups[i]) {
+				status = group_set(t, i, groups[i]);
+				if (status)
+					goto error;
+			}
+
+	return t;
+
+error:
+	rte_swx_table_selector_free(t);
+	return NULL;
+}
+
+
+static int
+group_check(struct table *t, struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct rte_swx_table_selector_member *e;
+		uint32_t n = 0;
+
+		/* Check group size. */
+		if (n_members >= t->params.n_members_per_group_max)
+			return -ENOSPC;
+
+		/* Check attributes of the current group member. */
+		if (elem->member_id >= t->params.n_members_per_group_max ||
+		    !elem->member_weight)
+			return -ENOSPC;
+
+		/* Check against duplicate member IDs. */
+		TAILQ_FOREACH(e, &group->members, node)
+			if (e->member_id == elem->member_id)
+				n++;
+
+		if (n != 1)
+			return -EINVAL;
+
+		/* Update group size. */
+		n_members++;
+	}
+
+	return 0;
+}
+
+static uint32_t
+members_read(struct group_member_info *members,
+	     struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct group_member_info *m = &members[n_members];
+
+		memset(m, 0, sizeof(struct group_member_info));
+
+		m->member_id = elem->member_id;
+		m->member_weight = elem->member_weight;
+		m->member_weight_normalized = elem->member_weight;
+
+		n_members++;
+	}
+
+	return n_members;
+}
+
+static uint32_t
+members_min_weight_find(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t min = UINT32_MAX, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight < min)
+			min = m->member_weight;
+	}
+
+	return min;
+}
+
+static uint32_t
+members_weight_divisor_check(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight_normalized % divisor)
+			return 0; /* FALSE. */
+	}
+
+	return 1; /* TRUE. */
+}
+
+static void
+members_weight_divisor_apply(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->member_weight_normalized /= divisor;
+	}
+}
+
+static uint32_t
+members_weight_sum(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t result = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		result += m->member_weight_normalized;
+	}
+
+	return result;
+}
+
+static void
+members_weight_scale(struct group_member_info *members,
+		     uint32_t n_members,
+		     uint32_t n_members_per_group_max,
+		     uint32_t weight_sum)
+{
+	uint32_t multiplier, remainder, i;
+
+	multiplier = n_members_per_group_max / weight_sum;
+	remainder = n_members_per_group_max % weight_sum;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->count = m->member_weight_normalized * multiplier;
+	}
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t min;
+
+		min = m->member_weight_normalized;
+		if (remainder < m->member_weight_normalized)
+			min = remainder;
+
+		m->count += min;
+		remainder -= min;
+		if (!remainder)
+			break;
+	}
+}
+
+static void
+members_write(struct group_member_info *members,
+	      uint32_t n_members,
+	      uint32_t *group_table)
+{
+	uint32_t pos = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t j;
+
+		for (j = 0; j < m->count; j++)
+			group_table[pos++] = m->member_id;
+	}
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group)
+{
+	uint32_t *gt = &t->group_table[group_id * t->params.n_members_per_group_max];
+	struct group_member_info *members = t->members;
+	uint32_t n_members, weight_min, weight_sum, divisor;
+	int status = 0;
+
+	/* Check input arguments. */
+	if (group_id >= t->params.n_groups_max)
+		return -EINVAL;
+
+	status = group_check(t, group);
+	if (status)
+		return status;
+
+	/* Read group members. */
+	n_members = members_read(members, group);
+
+	if (!n_members) {
+		memset(gt, 0, t->params.n_members_per_group_max * sizeof(uint32_t));
+
+		return 0;
+	}
+
+	/* Normalize weights. */
+	weight_min = members_min_weight_find(members, n_members);
+
+	for (divisor = 2; divisor <= weight_min; divisor++)
+		if (members_weight_divisor_check(members, n_members, divisor))
+			members_weight_divisor_apply(members, n_members, divisor);
+
+	/* Scale weights. */
+	weight_sum = members_weight_sum(members, n_members);
+	if (weight_sum > t->params.n_members_per_group_max)
+		return -ENOSPC;
+
+	members_weight_scale(members, n_members, t->params.n_members_per_group_max, weight_sum);
+
+	/* Write group members to the group table. */
+	members_write(members, n_members, gt);
+
+	return 0;
+}
+
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group)
+{
+	struct table *t = table;
+
+	return group_set(t, group_id, group);
+}
+
+struct mailbox {
+
+};
+
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox __rte_unused,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer)
+{
+	struct table *t = table;
+	uint32_t *group_id_ptr, *member_id_ptr, group_id, member_id, selector, group_member_index;
+
+	group_id_ptr = (uint32_t *)&(*group_id_buffer)[t->params.group_id_offset];
+
+	member_id_ptr = (uint32_t *)&(*member_id_buffer)[t->params.member_id_offset];
+
+	group_id = *group_id_ptr & (t->params.n_groups_max - 1);
+
+	selector = hash(&(*selector_buffer)[t->params.selector_offset],
+			t->params.selector_mask,
+			t->params.selector_size,
+			0);
+
+	group_member_index = selector & (t->params.n_members_per_group_max - 1);
+
+	member_id = t->group_table[(group_id << t->n_members_per_group_max_log2) +
+				   group_member_index];
+
+	*member_id_ptr = member_id;
+
+	return 1;
+}
diff --git a/lib/table/rte_swx_table_selector.h b/lib/table/rte_swx_table_selector.h
new file mode 100644
index 000000000..71b6a7481
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+#define __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Selector Table
+ *
+ * Selector table interface.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+#include "rte_swx_table.h"
+
+/** Selector table creation parameters. */
+struct rte_swx_table_selector_params {
+	/** Group ID offset. */
+	uint32_t group_id_offset;
+
+	/** Selector size in bytes. Must be non-zero. */
+	uint32_t selector_size;
+
+	/** Offset of the first byte of the selector within the selector buffer. */
+	uint32_t selector_offset;
+
+	/** Mask of *selector_size* bytes logically laid over the bytes at positions
+	 * selector_offset* .. (*selector_offset* + *selector_size* - 1) of the selector buffer in
+	 * order to specify which bits from the selector buffer are part of the selector and which
+	 * ones are not. A bit value of 1 in the *selector_mask* means the respective bit in the
+	 * selector buffer is part of the selector, while a bit value of 0 means the opposite. A
+	 * NULL value means that all the bits are part of the selector, i.e. the *selector_mask*
+	 * is an all-ones mask.
+	 */
+	uint8_t *selector_mask;
+
+	/** Member ID offset. */
+	uint32_t member_id_offset;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/** Group member parameters. */
+struct rte_swx_table_selector_member {
+	/** Linked list connectivity. */
+	TAILQ_ENTRY(rte_swx_table_selector_member) node;
+
+	/** Member ID. */
+	uint32_t member_id;
+
+	/** Member weight. */
+	uint32_t member_weight;
+};
+
+/** List of group members. */
+TAILQ_HEAD(rte_swx_table_selector_member_list, rte_swx_table_selector_member);
+
+/** Group parameters. */
+struct rte_swx_table_selector_group {
+	/** List of group members. */
+	struct rte_swx_table_selector_member_list members;
+};
+
+/**
+ * Selector table memory footprint get
+ *
+ * @param[in] n_groups_max
+ *   Maximum number of groups. Must be non-zero.
+ * @param[in] n_members_per_group_max
+ *   Maximum number of members per group. Must be non-zero.
+ * @return
+ *   Selector table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max);
+
+/**
+ * Selector table mailbox size get
+ *
+ * The mailbox is used to store the context of a select operation that is in
+ * progress and it is passed as a parameter to the select operation. This allows
+ * for multiple concurrent select operations into the same table.
+ *
+ * @return
+ *   Selector table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void);
+
+/**
+ * Selector table create
+ *
+ * @param[in] params
+ *   Selector table creation parameters.
+ * @param[in] groups
+ *   Groups to be added to the table at creation time. When NULL, it signifies that all groups are
+ *   invalid, otherwise it points to a pre-allocated array of size *n_groups_max*, where a NULL
+ *   element indicates that the associated group is invalid.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node);
+
+/**
+ * Group set
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] group_id
+ *   Group ID.
+ * @param[in] group
+ *   Group parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument(s);
+ *   -ENOSPC: Too many group members.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group);
+
+/**
+ * Selector table select
+ *
+ * This operation selects a member from the given group based on a hasing scheme.
+ *
+ * Multiple invocations of this function may be required in order to complete a single select
+ * operation for a given table and a given group ID. The completion of the operation is flagged by
+ * a return value of 1; in case of a return value of 0, the function must be invoked again with
+ * exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of each on-going  operation. The mailbox
+ * mechanism allows for multiple concurrent select operations into the same table.
+ *
+ * The typical reason an implementation may choose to split the operation into multiple steps is to
+ * hide the latency of the inherrent memory read operations: before a read operation with the
+ * source data likely not in the CPU cache, the source data prefetch is issued and the operation is
+ * postponed in favor of some other unrelated work, which the CPU executes in parallel with the
+ * source data being fetched into the CPU cache; later on, the operation is resumed, this time with
+ * the source data likely to be read from the CPU cache with no CPU pipeline stall, which
+ * significantly improves the operation performance.
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] group_id_buffer
+ *   Buffer where the input group ID is located at offset *group_id_offset*.
+ * @param[in] selector_buffer
+ *   Buffer where the key to select a member within the identified group is located starting from
+ *   offset *selector_offset*. Its size must be equal to the table *selector_size*.
+ * @param[in] member_id_buffer
+ *   Buffer where the output member ID is to be placed at offset *member_id_offset*.
+ * @return
+ *   0 when the operation is not yet completed, and 1 when the operation is complete. No other
+ *   return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer);
+
+/**
+ * Selector table free
+ *
+ * @param[in] table
+ *   Selector table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_selector_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index eb0291ac4..29301480c 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -28,4 +28,12 @@ EXPERIMENTAL {
 
 	# added in 21.05
 	rte_swx_table_wildcard_match_ops;
+
+	# added in 21.08
+	rte_swx_table_selector_create;
+	rte_swx_table_selector_footprint_get;
+	rte_swx_table_selector_free;
+	rte_swx_table_selector_group_set;
+	rte_swx_table_selector_mailbox_size_get;
+	rte_swx_table_selector_select;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V3 3/5] pipeline: add support for selector tables
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 2/5] table: add support for selector tables Cristian Dumitrescu
@ 2021-07-02 22:46     ` Cristian Dumitrescu
  2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 4/5] examples/pipeline: add support for selector tables Cristian Dumitrescu
                       ` (2 subsequent siblings)
  4 siblings, 1 reply; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:46 UTC (permalink / raw)
  To: dev

Add pipeline-level support for selector tables,

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.c           | 700 ++++++++++++++++++++++++-
 lib/pipeline/rte_swx_ctl.h           | 253 +++++++++
 lib/pipeline/rte_swx_pipeline.c      | 748 ++++++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h      |  51 ++
 lib/pipeline/rte_swx_pipeline_spec.c | 354 ++++++++++++-
 lib/pipeline/version.map             |  13 +
 6 files changed, 2034 insertions(+), 85 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c
index 5d04e750f..8cabce2b9 100644
--- a/lib/pipeline/rte_swx_ctl.c
+++ b/lib/pipeline/rte_swx_ctl.c
@@ -10,6 +10,8 @@
 #include <rte_common.h>
 #include <rte_byteorder.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_ctl.h"
 
 #define CHECK(condition, err_code)                                             \
@@ -89,11 +91,44 @@ struct table {
 	uint32_t n_delete;
 };
 
+struct selector {
+	/* Selector table info. */
+	struct rte_swx_ctl_selector_info info;
+
+	/* group_id field. */
+	struct rte_swx_ctl_table_match_field_info group_id_field;
+
+	/* selector fields. */
+	struct rte_swx_ctl_table_match_field_info *selector_fields;
+
+	/* member_id field. */
+	struct rte_swx_ctl_table_match_field_info member_id_field;
+
+	/* Current selector table. Array of info.n_groups_max elements.*/
+	struct rte_swx_table_selector_group **groups;
+
+	/* Pending selector table subject to the next commit. Array of info.n_groups_max elements.
+	 */
+	struct rte_swx_table_selector_group **pending_groups;
+
+	/* Valid flag per group. Array of n_groups_max elements. */
+	int *groups_added;
+
+	/* Pending delete flag per group. Group deletion is subject to the next commit. Array of
+	 * info.n_groups_max elements.
+	 */
+	int *groups_pending_delete;
+
+	/* Params. */
+	struct rte_swx_table_selector_params params;
+};
+
 struct rte_swx_ctl_pipeline {
 	struct rte_swx_ctl_pipeline_info info;
 	struct rte_swx_pipeline *p;
 	struct action *actions;
 	struct table *tables;
+	struct selector *selectors;
 	struct rte_swx_table_state *ts;
 	struct rte_swx_table_state *ts_next;
 	int numa_node;
@@ -709,6 +744,209 @@ table_free(struct rte_swx_ctl_pipeline *ctl)
 	ctl->tables = NULL;
 }
 
+static void
+selector_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->groups[group_id] = NULL;
+}
+
+static void
+selector_pending_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->pending_groups[group_id] = NULL;
+}
+
+static int
+selector_group_duplicate_to_pending(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *g, *gp;
+	struct rte_swx_table_selector_member *m;
+
+	selector_pending_group_members_free(s, group_id);
+
+	g = s->groups[group_id];
+	gp = s->pending_groups[group_id];
+
+	if (!gp) {
+		gp = calloc(1, sizeof(struct rte_swx_table_selector_group));
+		if (!gp)
+			goto error;
+
+		TAILQ_INIT(&gp->members);
+
+		s->pending_groups[group_id] = gp;
+	}
+
+	if (!g)
+		return 0;
+
+	TAILQ_FOREACH(m, &g->members, node) {
+		struct rte_swx_table_selector_member *mp;
+
+		mp = calloc(1, sizeof(struct rte_swx_table_selector_member));
+		if (!mp)
+			goto error;
+
+		memcpy(mp, m, sizeof(struct rte_swx_table_selector_member));
+
+		TAILQ_INSERT_TAIL(&gp->members, mp, node);
+	}
+
+	return 0;
+
+error:
+	selector_pending_group_members_free(s, group_id);
+	return -ENOMEM;
+}
+
+static void
+selector_free(struct rte_swx_ctl_pipeline *ctl)
+{
+	uint32_t i;
+
+	if (ctl->selectors)
+		return;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t i;
+
+		/* selector_fields. */
+		free(s->selector_fields);
+
+		/* groups. */
+		if (s->groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_group_members_free(s, i);
+
+		free(s->groups);
+
+		/* pending_groups. */
+		if (s->pending_groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_pending_group_members_free(s, i);
+
+		free(s->pending_groups);
+
+		/* groups_added. */
+		free(s->groups_added);
+
+		/* groups_pending_delete. */
+		free(s->groups_pending_delete);
+
+		/* params. */
+		free(s->params.selector_mask);
+	}
+
+	free(ctl->selectors);
+	ctl->selectors = NULL;
+}
+
+static struct selector *
+selector_find(struct rte_swx_ctl_pipeline *ctl, const char *selector_name)
+{
+	uint32_t i;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+
+		if (!strcmp(selector_name, s->info.name))
+			return s;
+	}
+
+	return NULL;
+}
+
+static int
+selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_ctl_table_match_field_info *first = NULL, *last = NULL;
+	uint8_t *selector_mask = NULL;
+	uint32_t selector_size = 0, selector_offset = 0, i;
+
+	/* Find first (smallest offset) and last (biggest offset) match fields. */
+	first = &s->selector_fields[0];
+	last = &s->selector_fields[0];
+
+	for (i = 1; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* selector_offset. */
+	selector_offset = first->offset / 8;
+
+	/* selector_size. */
+	selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* selector_mask. */
+	selector_mask = calloc(1, selector_size);
+	if (!selector_mask)
+		return -ENOMEM;
+
+	for (i = 0; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+		uint32_t start;
+		size_t size;
+
+		start = (f->offset - first->offset) / 8;
+		size = f->n_bits / 8;
+
+		memset(&selector_mask[start], 0xFF, size);
+	}
+
+	/* Fill in. */
+	s->params.group_id_offset = s->group_id_field.offset / 8;
+	s->params.selector_size = selector_size;
+	s->params.selector_offset = selector_offset;
+	s->params.selector_mask = selector_mask;
+	s->params.member_id_offset = s->member_id_field.offset / 8;
+	s->params.n_groups_max = s->info.n_groups_max;
+	s->params.n_members_per_group_max = s->info.n_members_per_group_max;
+
+	return 0;
+}
+
 static void
 table_state_free(struct rte_swx_ctl_pipeline *ctl)
 {
@@ -730,6 +968,15 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)
 			table->ops.free(ts->obj);
 	}
 
+	/* For each selector table, free its table state. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct rte_swx_table_state *ts = &ctl->ts_next[i];
+
+		/* Table object. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
 	free(ctl->ts_next);
 	ctl->ts_next = NULL;
 }
@@ -740,13 +987,14 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 	int status = 0;
 	uint32_t i;
 
-	ctl->ts_next = calloc(ctl->info.n_tables,
+	ctl->ts_next = calloc(ctl->info.n_tables + ctl->info.n_selectors,
 			      sizeof(struct rte_swx_table_state));
 	if (!ctl->ts_next) {
 		status = -ENOMEM;
 		goto error;
 	}
 
+	/* Tables. */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		struct table *table = &ctl->tables[i];
 		struct rte_swx_table_state *ts = &ctl->ts[i];
@@ -782,6 +1030,19 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 		ts_next->default_action_id = ts->default_action_id;
 	}
 
+	/* Selector tables. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + i];
+
+		/* Table object. */
+		ts_next->obj = rte_swx_table_selector_create(&s->params, NULL, ctl->numa_node);
+		if (!ts_next->obj) {
+			status = -ENODEV;
+			goto error;
+		}
+	}
+
 	return 0;
 
 error:
@@ -799,6 +1060,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)
 
 	table_state_free(ctl);
 
+	selector_free(ctl);
+
 	table_free(ctl);
 
 	free(ctl);
@@ -940,6 +1203,77 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)
 			goto error;
 	}
 
+	/* selector tables. */
+	ctl->selectors = calloc(ctl->info.n_selectors, sizeof(struct selector));
+	if (!ctl->selectors)
+		goto error;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t j;
+
+		/* info. */
+		status = rte_swx_ctl_selector_info_get(p, i, &s->info);
+		if (status)
+			goto error;
+
+		/* group_id field. */
+		status = rte_swx_ctl_selector_group_id_field_info_get(p,
+			i,
+			&s->group_id_field);
+		if (status)
+			goto error;
+
+		/* selector fields. */
+		s->selector_fields = calloc(s->info.n_selector_fields,
+			sizeof(struct rte_swx_ctl_table_match_field_info));
+		if (!s->selector_fields)
+			goto error;
+
+		for (j = 0; j < s->info.n_selector_fields; j++) {
+			status = rte_swx_ctl_selector_field_info_get(p,
+				i,
+				j,
+				&s->selector_fields[j]);
+			if (status)
+				goto error;
+		}
+
+		/* member_id field. */
+		status = rte_swx_ctl_selector_member_id_field_info_get(p,
+			i,
+			&s->member_id_field);
+		if (status)
+			goto error;
+
+		/* groups. */
+		s->groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->groups)
+			goto error;
+
+		/* pending_groups. */
+		s->pending_groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->pending_groups)
+			goto error;
+
+		/* groups_added. */
+		s->groups_added = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_added)
+			goto error;
+
+		/* groups_pending_delete. */
+		s->groups_pending_delete = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_pending_delete)
+			goto error;
+
+		/* params. */
+		status = selector_params_get(ctl, i);
+		if (status)
+			goto error;
+	}
+
 	/* ts. */
 	status = rte_swx_pipeline_table_state_get(p, &ctl->ts);
 	if (status)
@@ -1499,6 +1833,295 @@ table_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)
 	table_pending_default_free(table);
 }
 
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id)
+{
+	struct selector *s;
+	uint32_t i;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0] || !group_id)
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Find an unused group. */
+	for (i = 0; i < s->info.n_groups_max; i++)
+		if (!s->groups_added[i]) {
+			*group_id = i;
+			s->groups_added[i] = 1;
+			return 0;
+		}
+
+	return -ENOSPC;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id])
+		return -EINVAL;
+
+	/* Check if this group is already scheduled for deletion. */
+	if (s->groups_pending_delete[group_id])
+		return 0;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Schedule removal of all the members from the current group. */
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	/* Schedule the group for deletion. */
+	s->groups_pending_delete[group_id] = 1;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	if (!member_weight)
+		return rte_swx_ctl_pipeline_selector_group_member_delete(ctl,
+									 selector_name,
+									 group_id,
+									 member_id);
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id] ||
+	   s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* If this member is already in this group, then simply update its weight and return. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			m->member_weight = member_weight;
+			return 0;
+		}
+
+	/* Add new member to this group. */
+	m = calloc(1, sizeof(struct rte_swx_table_selector_member));
+	if (!m)
+		return -ENOMEM;
+
+	m->member_id = member_id;
+	m->member_weight = member_weight;
+
+	TAILQ_INSERT_TAIL(&group->members, m, node);
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id __rte_unused,
+						  uint32_t member_id __rte_unused)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	    (group_id >= s->info.n_groups_max) ||
+	    !s->groups_added[group_id] ||
+	    s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Look for this member in the group and remove it, if found. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			TAILQ_REMOVE(&group->members, m, node);
+			free(m);
+			return 0;
+		}
+
+	return 0;
+}
+
+static int
+selector_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Push pending group member changes (s->pending_groups[group_id]) to the selector table
+	 * mirror copy (ts_next->obj).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+		int status;
+
+		/* Skip this group if no change needed. */
+		if (!group)
+			continue;
+
+		/* Apply the pending changes for the current group. */
+		status = rte_swx_table_selector_group_set(ts_next->obj, group_id, group);
+		if (status)
+			return status;
+	}
+
+	return 0;
+}
+
+static void
+selector_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Commit pending group member changes (s->pending_groups[group_id]) to the stable group
+	 * records (s->groups[group_id).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *g = s->groups[group_id];
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		/* Skip this group if no change needed. */
+		if (!gp)
+			continue;
+
+		/* Transition the pending changes to stable. */
+		s->groups[group_id] = gp;
+		s->pending_groups[group_id] = NULL;
+
+		/* Free the old group member list. */
+		if (!g)
+			continue;
+
+		for ( ; ; ) {
+			struct rte_swx_table_selector_member *m;
+
+			m = TAILQ_FIRST(&g->members);
+			if (!m)
+				break;
+
+			TAILQ_REMOVE(&g->members, m, node);
+			free(m);
+		}
+
+		free(g);
+	}
+
+	/* Commit pending group validity changes (from s->groups_pending_delete[group_id] to
+	 * s->groups_added[group_id].
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		if (s->groups_pending_delete[group_id]) {
+			s->groups_added[group_id] = 0;
+			s->groups_pending_delete[group_id] = 0;
+		}
+}
+
+static void
+selector_rollback(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts = &ctl->ts[ctl->info.n_tables + selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Discard any previous changes to the selector table mirror copy (ts_next->obj). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		if (gp) {
+			ts_next->obj = ts->obj;
+			break;
+		}
+	}
+}
+
+static void
+selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Discard any pending group member changes (s->pending_groups[group_id]). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		selector_pending_group_members_free(s, group_id);
+
+	/* Discard any pending group deletions. */
+	memset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));
+}
+
 int
 rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 {
@@ -1508,8 +2131,8 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 
 	CHECK(ctl, EINVAL);
 
-	/* Operate the changes on the current ts_next before it becomes the new
-	 * ts.
+	/* Operate the changes on the current ts_next before it becomes the new ts. First, operate
+	 * all the changes that can fail; if no failure, then operate the changes that cannot fail.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		status = table_rollfwd0(ctl, i, 0);
@@ -1517,6 +2140,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			goto rollback;
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		status = selector_rollfwd(ctl, i);
+		if (status)
+			goto rollback;
+	}
+
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_rollfwd1(ctl, i);
 
@@ -1529,7 +2158,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 	ctl->ts = ctl->ts_next;
 	ctl->ts_next = ts;
 
-	/* Operate the changes on the current ts_next, which is the previous ts.
+	/* Operate the changes on the current ts_next, which is the previous ts, in order to get
+	 * the current ts_next in sync with the current ts. Since the changes that can fail did
+	 * not fail on the previous ts_next, it is guaranteed that they will not fail on the
+	 * current ts_next, hence no error checking is needed.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		table_rollfwd0(ctl, i, 1);
@@ -1537,6 +2169,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 		table_rollfwd2(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollfwd(ctl, i);
+		selector_rollfwd_finalize(ctl, i);
+	}
+
 	return 0;
 
 rollback:
@@ -1546,6 +2183,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			table_abort(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollback(ctl, i);
+		if (abort_on_fail)
+			selector_abort(ctl, i);
+	}
+
 	return status;
 }
 
@@ -1559,6 +2202,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)
 
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_abort(ctl, i);
+
+	for (i = 0; i < ctl->info.n_selectors; i++)
+		selector_abort(ctl, i);
 }
 
 static int
@@ -1858,3 +2504,49 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 		n_entries);
 	return 0;
 }
+
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name)
+{
+	struct selector *s;
+	uint32_t group_id;
+
+	if (!f || !ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Selector. */
+	fprintf(f, "# Selector %s: max groups %u, max members per group %u\n",
+		s->info.name,
+		s->info.n_groups_max,
+		s->info.n_members_per_group_max);
+
+	/* Groups. */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->groups[group_id];
+		struct rte_swx_table_selector_member *m;
+		uint32_t n_members = 0;
+
+		fprintf(f, "Group %u = [", group_id);
+
+		/* Non-empty group. */
+		if (group)
+			TAILQ_FOREACH(m, &group->members, node) {
+				fprintf(f, "%u:%u ", m->member_id, m->member_weight);
+				n_members++;
+			}
+
+		/* Empty group. */
+		if (!n_members)
+			fprintf(f, "0:1 ");
+
+		fprintf(f, "]\n");
+	}
+
+	return 0;
+}
diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index dee788be8..f37301cf9 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -22,6 +22,7 @@ extern "C" {
 
 #include "rte_swx_port.h"
 #include "rte_swx_table.h"
+#include "rte_swx_table_selector.h"
 
 struct rte_swx_pipeline;
 
@@ -48,6 +49,9 @@ struct rte_swx_ctl_pipeline_info {
 	/** Number of tables. */
 	uint32_t n_tables;
 
+	/** Number of selector tables. */
+	uint32_t n_selectors;
+
 	/** Number of register arrays. */
 	uint32_t n_regarrays;
 
@@ -385,6 +389,129 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 				      const char *table_name,
 				      struct rte_swx_table_stats *stats);
 
+/*
+ * Selector Table Query API.
+ */
+
+/** Selector info. */
+struct rte_swx_ctl_selector_info {
+	/** Selector table name. */
+	char name[RTE_SWX_CTL_NAME_SIZE];
+
+	/** Number of selector fields. */
+	uint32_t n_selector_fields;
+
+	/** Maximum number of groups. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Selector table info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors* - 1).
+ * @param[out] selector
+ *   Selector table info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector);
+
+/**
+ * Selector table "group ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "group ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+					     uint32_t selector_id,
+					     struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Sselector table selector field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[in] selector_field_id
+ *   Selector table selector field ID (0 .. *n_selector_fields* - 1).
+ * @param[out] field
+ *   Selector table selector field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+				    uint32_t selector_id,
+				    uint32_t selector_field_id,
+				    struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Selector table "member ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "member ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+					      uint32_t selector_id,
+					      struct rte_swx_ctl_table_match_field_info *field);
+
+/** Selector table statistics. */
+struct rte_swx_pipeline_selector_stats {
+	/** Number of packets. */
+	uint64_t n_pkts;
+};
+
+/**
+ * Selector table statistics counters read
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] stats
+ *   Selector table stats. Must point to a pre-allocated structure.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+					 const char *selector_name,
+					 struct rte_swx_pipeline_selector_stats *stats);
+
 /*
  * Table Update API.
  */
@@ -529,6 +656,111 @@ rte_swx_ctl_pipeline_table_entry_delete(struct rte_swx_ctl_pipeline *ctl,
 					const char *table_name,
 					struct rte_swx_table_entry *entry);
 
+/**
+ * Pipeline selector table group add
+ *
+ * Add a new group to a selector table. This operation is executed before this
+ * function returns and its result is independent of the result of the next
+ * commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] group_id
+ *   The ID of the new group. Only valid when the function call is successful.
+ *   This group is initially empty, i.e. it does not contain any members.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOSPC: All groups are currently in use, no group available.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id);
+
+/**
+ * Pipeline selector table group delete
+ *
+ * Schedule a group for deletion as part of the next commit operation. The group
+ * to be deleted can be empty or non-empty.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   Group to be deleted from the selector table.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id);
+
+/**
+ * Pipeline selector table member add to group
+ *
+ * Schedule the operation to add a new member to an existing group as part of
+ * the next commit operation. If this member is already in this group, the
+ * member weight is updated to the new value. A weight of zero means this member
+ * is to be deleted from the group.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID.
+ * @param[in] member_id
+ *   The member to be added to the group.
+ * @param[in] member_weight
+ *   Member weight.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory;
+ *   -ENOSPC: The group is full.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight);
+
+/**
+ * Pipeline selector table member delete from group
+ *
+ * Schedule the operation to delete a member from an existing group as part of
+ * the next commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID. Must be valid.
+ * @param[in] member_id
+ *   The member to be added to the group. Must be valid.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id,
+						  uint32_t member_id);
+
 /**
  * Pipeline commit
  *
@@ -608,6 +840,27 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 				   struct rte_swx_ctl_pipeline *ctl,
 				   const char *table_name);
 
+/**
+ * Pipeline selector print to file
+ *
+ * Print all the selector entries to file.
+ *
+ * @param[in] f
+ *   Output file.
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name);
+
 /*
  * Register Array Query API.
  */
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index a2732a1e5..22c860f28 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -15,6 +15,8 @@
 #include <rte_cycles.h>
 #include <rte_meter.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_pipeline.h"
 #include "rte_swx_ctl.h"
 
@@ -498,6 +500,7 @@ enum instruction_type {
 
 	/* table TABLE */
 	INSTR_TABLE,
+	INSTR_SELECTOR,
 
 	/* extern e.obj.func */
 	INSTR_EXTERN_OBJ,
@@ -794,6 +797,38 @@ struct table_statistics {
 	uint64_t *n_pkts_action;
 };
 
+/*
+ * Selector.
+ */
+struct selector {
+	TAILQ_ENTRY(selector) node;
+	char name[RTE_SWX_NAME_SIZE];
+
+	struct field *group_id_field;
+	struct field **selector_fields;
+	uint32_t n_selector_fields;
+	struct header *selector_header;
+	struct field *member_id_field;
+
+	uint32_t n_groups_max;
+	uint32_t n_members_per_group_max;
+
+	uint32_t id;
+};
+
+TAILQ_HEAD(selector_tailq, selector);
+
+struct selector_runtime {
+	void *mailbox;
+	uint8_t **group_id_buffer;
+	uint8_t **selector_buffer;
+	uint8_t **member_id_buffer;
+};
+
+struct selector_statistics {
+	uint64_t n_pkts;
+};
+
 /*
  * Register array.
  */
@@ -873,6 +908,7 @@ struct thread {
 
 	/* Tables. */
 	struct table_runtime *tables;
+	struct selector_runtime *selectors;
 	struct rte_swx_table_state *table_state;
 	uint64_t action_id;
 	int hit; /* 0 = Miss, 1 = Hit. */
@@ -1308,6 +1344,7 @@ struct rte_swx_pipeline {
 	struct action_tailq actions;
 	struct table_type_tailq table_types;
 	struct table_tailq tables;
+	struct selector_tailq selectors;
 	struct regarray_tailq regarrays;
 	struct meter_profile_tailq meter_profiles;
 	struct metarray_tailq metarrays;
@@ -1317,6 +1354,7 @@ struct rte_swx_pipeline {
 	struct instruction **action_instructions;
 	struct rte_swx_table_state *table_state;
 	struct table_statistics *table_stats;
+	struct selector_statistics *selector_stats;
 	struct regarray_runtime *regarray_runtime;
 	struct metarray_runtime *metarray_runtime;
 	struct instruction *instructions;
@@ -1329,6 +1367,7 @@ struct rte_swx_pipeline {
 	uint32_t n_extern_funcs;
 	uint32_t n_actions;
 	uint32_t n_tables;
+	uint32_t n_selectors;
 	uint32_t n_regarrays;
 	uint32_t n_metarrays;
 	uint32_t n_headers;
@@ -3450,6 +3489,9 @@ instr_hdr_invalidate_exec(struct rte_swx_pipeline *p)
 static struct table *
 table_find(struct rte_swx_pipeline *p, const char *name);
 
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name);
+
 static int
 instr_table_translate(struct rte_swx_pipeline *p,
 		      struct action *action,
@@ -3459,16 +3501,26 @@ instr_table_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct table *t;
+	struct selector *s;
 
 	CHECK(!action, EINVAL);
 	CHECK(n_tokens == 2, EINVAL);
 
 	t = table_find(p, tokens[1]);
-	CHECK(t, EINVAL);
+	if (t) {
+		instr->type = INSTR_TABLE;
+		instr->table.table_id = t->id;
+		return 0;
+	}
 
-	instr->type = INSTR_TABLE;
-	instr->table.table_id = t->id;
-	return 0;
+	s = selector_find(p, tokens[1]);
+	if (s) {
+		instr->type = INSTR_SELECTOR;
+		instr->table.table_id = s->id;
+		return 0;
+	}
+
+	CHECK(0, EINVAL);
 }
 
 static inline void
@@ -3522,6 +3574,45 @@ instr_table_exec(struct rte_swx_pipeline *p)
 	thread_ip_action_call(p, t, action_id);
 }
 
+static inline void
+instr_selector_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+	uint32_t selector_id = ip->table.table_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables + selector_id];
+	struct selector_runtime *selector = &t->selectors[selector_id];
+	struct selector_statistics *stats = &p->selector_stats[selector_id];
+	uint64_t n_pkts = stats->n_pkts;
+	int done;
+
+	/* Table. */
+	done = rte_swx_table_selector_select(ts->obj,
+			   selector->mailbox,
+			   selector->group_id_buffer,
+			   selector->selector_buffer,
+			   selector->member_id_buffer);
+	if (!done) {
+		/* Thread. */
+		TRACE("[Thread %2u] selector %u (not finalized)\n",
+		      p->thread_id,
+		      selector_id);
+
+		thread_yield(p);
+		return;
+	}
+
+
+	TRACE("[Thread %2u] selector %u\n",
+	      p->thread_id,
+	      selector_id);
+
+	stats->n_pkts = n_pkts + 1;
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * extern.
  */
@@ -8787,6 +8878,7 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_METER_IMI] = instr_meter_imi_exec,
 
 	[INSTR_TABLE] = instr_table_exec,
+	[INSTR_SELECTOR] = instr_selector_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
 
@@ -9253,6 +9345,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 	CHECK_NAME(name, EINVAL);
 	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
 
 	CHECK(params, EINVAL);
 
@@ -9455,82 +9548,6 @@ table_params_free(struct rte_swx_table_params *params)
 	free(params);
 }
 
-static int
-table_state_build(struct rte_swx_pipeline *p)
-{
-	struct table *table;
-
-	p->table_state = calloc(p->n_tables,
-				sizeof(struct rte_swx_table_state));
-	CHECK(p->table_state, ENOMEM);
-
-	TAILQ_FOREACH(table, &p->tables, node) {
-		struct rte_swx_table_state *ts = &p->table_state[table->id];
-
-		if (table->type) {
-			struct rte_swx_table_params *params;
-
-			/* ts->obj. */
-			params = table_params_get(table);
-			CHECK(params, ENOMEM);
-
-			ts->obj = table->type->ops.create(params,
-				NULL,
-				table->args,
-				p->numa_node);
-
-			table_params_free(params);
-			CHECK(ts->obj, ENODEV);
-		}
-
-		/* ts->default_action_data. */
-		if (table->action_data_size_max) {
-			ts->default_action_data =
-				malloc(table->action_data_size_max);
-			CHECK(ts->default_action_data, ENOMEM);
-
-			memcpy(ts->default_action_data,
-			       table->default_action_data,
-			       table->action_data_size_max);
-		}
-
-		/* ts->default_action_id. */
-		ts->default_action_id = table->default_action->id;
-	}
-
-	return 0;
-}
-
-static void
-table_state_build_free(struct rte_swx_pipeline *p)
-{
-	uint32_t i;
-
-	if (!p->table_state)
-		return;
-
-	for (i = 0; i < p->n_tables; i++) {
-		struct rte_swx_table_state *ts = &p->table_state[i];
-		struct table *table = table_find_by_id(p, i);
-
-		/* ts->obj. */
-		if (table->type && ts->obj)
-			table->type->ops.free(ts->obj);
-
-		/* ts->default_action_data. */
-		free(ts->default_action_data);
-	}
-
-	free(p->table_state);
-	p->table_state = NULL;
-}
-
-static void
-table_state_free(struct rte_swx_pipeline *p)
-{
-	table_state_build_free(p);
-}
-
 static int
 table_stub_lkp(void *table __rte_unused,
 	       void *mailbox __rte_unused,
@@ -9658,6 +9675,458 @@ table_free(struct rte_swx_pipeline *p)
 	}
 }
 
+/*
+ * Selector.
+ */
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name)
+{
+	struct selector *s;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (strcmp(s->name, name) == 0)
+			return s;
+
+	return NULL;
+}
+
+static struct selector *
+selector_find_by_id(struct rte_swx_pipeline *p, uint32_t id)
+{
+	struct selector *s = NULL;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (s->id == id)
+			return s;
+
+	return NULL;
+}
+
+static int
+selector_fields_check(struct rte_swx_pipeline *p,
+		      struct rte_swx_pipeline_selector_params *params,
+		      struct header **header)
+{
+	struct header *h0 = NULL;
+	struct field *hf, *mf;
+	uint32_t i;
+
+	/* Return if no selector fields. */
+	if (!params->n_selector_fields || !params->selector_field_names)
+		return -EINVAL;
+
+	/* Check that all the selector fields either belong to the same header
+	 * or are all meta-data fields.
+	 */
+	hf = header_field_parse(p, params->selector_field_names[0], &h0);
+	mf = metadata_field_parse(p, params->selector_field_names[0]);
+	if (!hf && !mf)
+		return -EINVAL;
+
+	for (i = 1; i < params->n_selector_fields; i++)
+		if (h0) {
+			struct header *h;
+
+			hf = header_field_parse(p, params->selector_field_names[i], &h);
+			if (!hf || (h->id != h0->id))
+				return -EINVAL;
+		} else {
+			mf = metadata_field_parse(p, params->selector_field_names[i]);
+			if (!mf)
+				return -EINVAL;
+		}
+
+	/* Check that there are no duplicated match fields. */
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+		uint32_t j;
+
+		for (j = i + 1; j < params->n_selector_fields; j++)
+			if (!strcmp(params->selector_field_names[j], field_name))
+				return -EINVAL;
+	}
+
+	/* Return. */
+	if (header)
+		*header = h0;
+
+	return 0;
+}
+
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params)
+{
+	struct selector *s;
+	struct header *selector_header = NULL;
+	struct field *group_id_field, *member_id_field;
+	uint32_t i;
+	int status = 0;
+
+	CHECK(p, EINVAL);
+
+	CHECK_NAME(name, EINVAL);
+	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
+
+	CHECK(params, EINVAL);
+
+	CHECK_NAME(params->group_id_field_name, EINVAL);
+	group_id_field = metadata_field_parse(p, params->group_id_field_name);
+	CHECK(group_id_field, EINVAL);
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		CHECK_NAME(field_name, EINVAL);
+	}
+	status = selector_fields_check(p, params, &selector_header);
+	if (status)
+		return status;
+
+	CHECK_NAME(params->member_id_field_name, EINVAL);
+	member_id_field = metadata_field_parse(p, params->member_id_field_name);
+	CHECK(member_id_field, EINVAL);
+
+	CHECK(params->n_groups_max, EINVAL);
+
+	CHECK(params->n_members_per_group_max, EINVAL);
+
+	/* Memory allocation. */
+	s = calloc(1, sizeof(struct selector));
+	if (!s) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->selector_fields = calloc(params->n_selector_fields, sizeof(struct field *));
+	if (!s->selector_fields) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Node initialization. */
+	strcpy(s->name, name);
+
+	s->group_id_field = group_id_field;
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		s->selector_fields[i] = selector_header ?
+			header_field_parse(p, field_name, NULL) :
+			metadata_field_parse(p, field_name);
+	}
+
+	s->n_selector_fields = params->n_selector_fields;
+
+	s->selector_header = selector_header;
+
+	s->member_id_field = member_id_field;
+
+	s->n_groups_max = params->n_groups_max;
+
+	s->n_members_per_group_max = params->n_members_per_group_max;
+
+	s->id = p->n_selectors;
+
+	/* Node add to tailq. */
+	TAILQ_INSERT_TAIL(&p->selectors, s, node);
+	p->n_selectors++;
+
+	return 0;
+
+error:
+	if (!s)
+		return status;
+
+	free(s->selector_fields);
+
+	free(s);
+
+	return status;
+}
+
+static void
+selector_params_free(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return;
+
+	free(params->selector_mask);
+
+	free(params);
+}
+
+static struct rte_swx_table_selector_params *
+selector_table_params_get(struct selector *s)
+{
+	struct rte_swx_table_selector_params *params = NULL;
+	struct field *first, *last;
+	uint32_t i;
+
+	/* Memory allocation. */
+	params = calloc(1, sizeof(struct rte_swx_pipeline_selector_params));
+	if (!params)
+		goto error;
+
+	/* Group ID. */
+	params->group_id_offset = s->group_id_field->offset / 8;
+
+	/* Find first (smallest offset) and last (biggest offset) selector fields. */
+	first = s->selector_fields[0];
+	last = s->selector_fields[0];
+
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* Selector offset and size. */
+	params->selector_offset = first->offset / 8;
+	params->selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* Memory allocation. */
+	params->selector_mask = calloc(1, params->selector_size);
+	if (!params->selector_mask)
+		goto error;
+
+	/* Selector mask. */
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+		uint32_t start = (f->offset - first->offset) / 8;
+		size_t size = f->n_bits / 8;
+
+		memset(&params->selector_mask[start], 0xFF, size);
+	}
+
+	/* Member ID. */
+	params->member_id_offset = s->member_id_field->offset / 8;
+
+	/* Maximum number of groups. */
+	params->n_groups_max = s->n_groups_max;
+
+	/* Maximum number of members per group. */
+	params->n_members_per_group_max = s->n_members_per_group_max;
+
+	return params;
+
+error:
+	selector_params_free(params);
+	return NULL;
+}
+
+static void
+selector_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		uint32_t j;
+
+		if (!t->selectors)
+			continue;
+
+		for (j = 0; j < p->n_selectors; j++) {
+			struct selector_runtime *r = &t->selectors[j];
+
+			free(r->mailbox);
+		}
+
+		free(t->selectors);
+		t->selectors = NULL;
+	}
+
+	free(p->selector_stats);
+	p->selector_stats = NULL;
+}
+
+static int
+selector_build(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+	int status = 0;
+
+	/* Per pipeline: selector statistics. */
+	p->selector_stats = calloc(p->n_selectors, sizeof(struct selector_statistics));
+	if (!p->selector_stats) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Per thread: selector run-time. */
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		struct selector *s;
+
+		t->selectors = calloc(p->n_selectors, sizeof(struct selector_runtime));
+		if (!t->selectors) {
+			status = -ENOMEM;
+			goto error;
+		}
+
+		TAILQ_FOREACH(s, &p->selectors, node) {
+			struct selector_runtime *r = &t->selectors[s->id];
+			uint64_t size;
+
+			/* r->mailbox. */
+			size = rte_swx_table_selector_mailbox_size_get();
+			if (size) {
+				r->mailbox = calloc(1, size);
+				if (!r->mailbox) {
+					status = -ENOMEM;
+					goto error;
+				}
+			}
+
+			/* r->group_id_buffer. */
+			r->group_id_buffer = &t->structs[p->metadata_struct_id];
+
+			/* r->selector_buffer. */
+			r->selector_buffer = s->selector_header ?
+				&t->structs[s->selector_header->struct_id] :
+				&t->structs[p->metadata_struct_id];
+
+			/* r->member_id_buffer. */
+			r->member_id_buffer = &t->structs[p->metadata_struct_id];
+		}
+	}
+
+	return 0;
+
+error:
+	selector_build_free(p);
+	return status;
+}
+
+static void
+selector_free(struct rte_swx_pipeline *p)
+{
+	selector_build_free(p);
+
+	/* Selector tables. */
+	for ( ; ; ) {
+		struct selector *elem;
+
+		elem = TAILQ_FIRST(&p->selectors);
+		if (!elem)
+			break;
+
+		TAILQ_REMOVE(&p->selectors, elem, node);
+		free(elem->selector_fields);
+		free(elem);
+	}
+}
+
+/*
+ * Table state.
+ */
+static int
+table_state_build(struct rte_swx_pipeline *p)
+{
+	struct table *table;
+	struct selector *s;
+
+	p->table_state = calloc(p->n_tables + p->n_selectors,
+				sizeof(struct rte_swx_table_state));
+	CHECK(p->table_state, ENOMEM);
+
+	TAILQ_FOREACH(table, &p->tables, node) {
+		struct rte_swx_table_state *ts = &p->table_state[table->id];
+
+		if (table->type) {
+			struct rte_swx_table_params *params;
+
+			/* ts->obj. */
+			params = table_params_get(table);
+			CHECK(params, ENOMEM);
+
+			ts->obj = table->type->ops.create(params,
+				NULL,
+				table->args,
+				p->numa_node);
+
+			table_params_free(params);
+			CHECK(ts->obj, ENODEV);
+		}
+
+		/* ts->default_action_data. */
+		if (table->action_data_size_max) {
+			ts->default_action_data =
+				malloc(table->action_data_size_max);
+			CHECK(ts->default_action_data, ENOMEM);
+
+			memcpy(ts->default_action_data,
+			       table->default_action_data,
+			       table->action_data_size_max);
+		}
+
+		/* ts->default_action_id. */
+		ts->default_action_id = table->default_action->id;
+	}
+
+	TAILQ_FOREACH(s, &p->selectors, node) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
+		struct rte_swx_table_selector_params *params;
+
+		/* ts->obj. */
+		params = selector_table_params_get(s);
+		CHECK(params, ENOMEM);
+
+		ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+
+		selector_params_free(params);
+		CHECK(ts->obj, ENODEV);
+	}
+
+	return 0;
+}
+
+static void
+table_state_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	if (!p->table_state)
+		return;
+
+	for (i = 0; i < p->n_tables; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[i];
+		struct table *table = table_find_by_id(p, i);
+
+		/* ts->obj. */
+		if (table->type && ts->obj)
+			table->type->ops.free(ts->obj);
+
+		/* ts->default_action_data. */
+		free(ts->default_action_data);
+	}
+
+	for (i = 0; i < p->n_selectors; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];
+
+		/* ts->obj. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
+	free(p->table_state);
+	p->table_state = NULL;
+}
+
+static void
+table_state_free(struct rte_swx_pipeline *p)
+{
+	table_state_build_free(p);
+}
+
 /*
  * Register array.
  */
@@ -9988,6 +10457,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)
 	TAILQ_INIT(&pipeline->actions);
 	TAILQ_INIT(&pipeline->table_types);
 	TAILQ_INIT(&pipeline->tables);
+	TAILQ_INIT(&pipeline->selectors);
 	TAILQ_INIT(&pipeline->regarrays);
 	TAILQ_INIT(&pipeline->meter_profiles);
 	TAILQ_INIT(&pipeline->metarrays);
@@ -10010,6 +10480,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)
 	metarray_free(p);
 	regarray_free(p);
 	table_state_free(p);
+	selector_free(p);
 	table_free(p);
 	action_free(p);
 	metadata_free(p);
@@ -10089,6 +10560,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	if (status)
 		goto error;
 
+	status = selector_build(p);
+	if (status)
+		goto error;
+
 	status = table_state_build(p);
 	if (status)
 		goto error;
@@ -10108,6 +10583,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	metarray_build_free(p);
 	regarray_build_free(p);
 	table_state_build_free(p);
+	selector_build_free(p);
 	table_build_free(p);
 	action_build_free(p);
 	metadata_build_free(p);
@@ -10167,6 +10643,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,
 	pipeline->n_ports_out = p->n_ports_out;
 	pipeline->n_actions = n_actions;
 	pipeline->n_tables = n_tables;
+	pipeline->n_selectors = p->n_selectors;
 	pipeline->n_regarrays = p->n_regarrays;
 	pipeline->n_metarrays = p->n_metarrays;
 
@@ -10320,6 +10797,98 @@ rte_swx_ctl_table_ops_get(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector)
+{
+	struct selector *s = NULL;
+
+	if (!p || !selector)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	strcpy(selector->name, s->name);
+
+	selector->n_selector_fields = s->n_selector_fields;
+	selector->n_groups_max = s->n_groups_max;
+	selector->n_members_per_group_max = s->n_members_per_group_max;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->group_id_field->n_bits;
+	field->offset = s->group_id_field->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 uint32_t selector_field_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+	struct field *f;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s || (selector_field_id >= s->n_selector_fields))
+		return -EINVAL;
+
+	f = s->selector_fields[selector_field_id];
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = s->selector_header ? 1 : 0;
+	field->n_bits = f->n_bits;
+	field->offset = f->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->member_id_field->n_bits;
+	field->offset = s->member_id_field->offset;
+
+	return 0;
+}
+
 int
 rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
 				 struct rte_swx_table_state **table_state)
@@ -10405,6 +10974,25 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+	const char *selector_name,
+	struct rte_swx_pipeline_selector_stats *stats)
+{
+	struct selector *s;
+
+	if (!p || !selector_name || !selector_name[0] || !stats)
+		return -EINVAL;
+
+	s = selector_find(p, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	stats->n_pkts = p->selector_stats[s->id].n_pkts;
+
+	return 0;
+}
+
 int
 rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,
 			      uint32_t regarray_id,
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index feeb10a5c..cd395ac39 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -612,6 +612,57 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 			      const char *args,
 			      uint32_t size);
 
+/** Pipeline selector table parameters. */
+struct rte_swx_pipeline_selector_params {
+	/** The group ID field. Input into the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *group_id_field_name;
+
+	/** The set of fields used to select (through a hashing scheme) the
+	 * member within the current group. Inputs into the seletion operation.
+	 * Restriction: All the selector fields must be part of the same struct,
+	 * i.e. part of the same header or part of the meta-data structure.
+	 */
+	const char **selector_field_names;
+
+	/** The number of selector fields. Must be non-zero. */
+	uint32_t n_selector_fields;
+
+	/** The member ID field. Output from the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *member_id_field_name;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Pipeline selector table configure
+ *
+ * @param[out] p
+ *   Pipeline handle.
+ * @param[in] name
+ *   Selector table name.
+ * @param[in] params
+ *   Selector table parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough space/cannot allocate memory;
+ *   -EEXIST: Selector table with this name already exists;
+ *   -ENODEV: Selector table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params);
+
 /**
  * Pipeline register array configure
  *
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index 2e867d7bf..6980b0390 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -18,7 +18,9 @@
 #define TABLE_BLOCK 2
 #define TABLE_KEY_BLOCK 3
 #define TABLE_ACTIONS_BLOCK 4
-#define APPLY_BLOCK 5
+#define SELECTOR_BLOCK 5
+#define SELECTOR_SELECTOR_BLOCK 6
+#define APPLY_BLOCK 7
 
 /*
  * extobj.
@@ -940,6 +942,307 @@ table_block_parse(struct table_spec *s,
 	return -EINVAL;
 }
 
+/*
+ * selector.
+ *
+ * selector SELECTOR_NAME {
+ *	group_id FIELD_NAME
+ *	selector {
+ *		FIELD_NAME
+ *		...
+ *	}
+ *	member_id FIELD_NAME
+ *	n_groups N_GROUPS
+ *	n_members_per_group N_MEMBERS_PER_GROUP
+ * }
+ */
+struct selector_spec {
+	char *name;
+	struct rte_swx_pipeline_selector_params params;
+};
+
+static void
+selector_spec_free(struct selector_spec *s)
+{
+	uintptr_t field_name;
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	/* name. */
+	free(s->name);
+	s->name = NULL;
+
+	/* params->group_id_field_name. */
+	field_name = (uintptr_t)s->params.group_id_field_name;
+	free((void *)field_name);
+	s->params.group_id_field_name = NULL;
+
+	/* params->selector_field_names. */
+	for (i = 0; i < s->params.n_selector_fields; i++) {
+		field_name = (uintptr_t)s->params.selector_field_names[i];
+
+		free((void *)field_name);
+	}
+
+	free(s->params.selector_field_names);
+	s->params.selector_field_names = NULL;
+
+	s->params.n_selector_fields = 0;
+
+	/* params->member_id_field_name. */
+	field_name = (uintptr_t)s->params.member_id_field_name;
+	free((void *)field_name);
+	s->params.member_id_field_name = NULL;
+
+	/* params->n_groups_max. */
+	s->params.n_groups_max = 0;
+
+	/* params->n_members_per_group_max. */
+	s->params.n_members_per_group_max = 0;
+}
+
+static int
+selector_statement_parse(struct selector_spec *s,
+			 uint32_t *block_mask,
+			 char **tokens,
+			 uint32_t n_tokens,
+			 uint32_t n_lines,
+			 uint32_t *err_line,
+			 const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 3) || strcmp(tokens[2], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	if (!s->name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_statement_parse(uint32_t *block_mask,
+				  char **tokens,
+				  uint32_t n_tokens,
+				  uint32_t n_lines,
+				  uint32_t *err_line,
+				  const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_block_parse(struct selector_spec *s,
+			      uint32_t *block_mask,
+			      char **tokens,
+			      uint32_t n_tokens,
+			      uint32_t n_lines,
+			      uint32_t *err_line,
+			      const char **err_msg)
+{
+	const char **new_fields;
+	char *name;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_SELECTOR_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector field statement.";
+		return -EINVAL;
+	}
+
+	name = strdup(tokens[0]);
+	if (!name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_fields = realloc(s->params.selector_field_names,
+			     (s->params.n_selector_fields + 1) * sizeof(char *));
+	if (!new_fields) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.selector_field_names = new_fields;
+	s->params.selector_field_names[s->params.n_selector_fields] = name;
+	s->params.n_selector_fields++;
+
+	return 0;
+}
+
+static int
+selector_block_parse(struct selector_spec *s,
+		     uint32_t *block_mask,
+		     char **tokens,
+		     uint32_t n_tokens,
+		     uint32_t n_lines,
+		     uint32_t *err_line,
+		     const char **err_msg)
+{
+	if (*block_mask & (1 << SELECTOR_SELECTOR_BLOCK))
+		return selector_selector_block_parse(s,
+						     block_mask,
+						     tokens,
+						     n_tokens,
+						     n_lines,
+						     err_line,
+						     err_msg);
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_BLOCK);
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "group_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid group_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.group_id_field_name = strdup(tokens[1]);
+		if (!s->params.group_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "selector"))
+		return selector_selector_statement_parse(block_mask,
+							 tokens,
+							 n_tokens,
+							 n_lines,
+							 err_line,
+							 err_msg);
+
+	if (!strcmp(tokens[0], "member_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid member_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.member_id_field_name = strdup(tokens[1]);
+		if (!s->params.member_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_groups_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_groups_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_members_per_group_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_members_per_group_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* Anything else. */
+	if (err_line)
+		*err_line = n_lines;
+	if (err_msg)
+		*err_msg = "Invalid statement.";
+	return -EINVAL;
+}
+
 /*
  * regarray.
  *
@@ -1203,6 +1506,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	struct metadata_spec metadata_spec = {0};
 	struct action_spec action_spec = {0};
 	struct table_spec table_spec = {0};
+	struct selector_spec selector_spec = {0};
 	struct regarray_spec regarray_spec = {0};
 	struct metarray_spec metarray_spec = {0};
 	struct apply_spec apply_spec = {0};
@@ -1386,6 +1690,38 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector block. */
+		if (block_mask & (1 << SELECTOR_BLOCK)) {
+			status = selector_block_parse(&selector_spec,
+						      &block_mask,
+						      tokens,
+						      n_tokens,
+						      n_lines,
+						      err_line,
+						      err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << SELECTOR_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_selector_config(p,
+				selector_spec.name,
+				&selector_spec.params);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Selector configuration error.";
+				goto error;
+			}
+
+			selector_spec_free(&selector_spec);
+
+			continue;
+		}
+
 		/* apply block. */
 		if (block_mask & (1 << APPLY_BLOCK)) {
 			status = apply_block_parse(&apply_spec,
@@ -1544,6 +1880,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector. */
+		if (!strcmp(tokens[0], "selector")) {
+			status = selector_statement_parse(&selector_spec,
+							  &block_mask,
+							  tokens,
+							  n_tokens,
+							  n_lines,
+							  err_line,
+							  err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
 		/* regarray. */
 		if (!strcmp(tokens[0], "regarray")) {
 			status = regarray_statement_parse(&regarray_spec,
@@ -1651,6 +2002,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	metadata_spec_free(&metadata_spec);
 	action_spec_free(&action_spec);
 	table_spec_free(&table_spec);
+	selector_spec_free(&selector_spec);
 	regarray_spec_free(&regarray_spec);
 	metarray_spec_free(&metarray_spec);
 	apply_spec_free(&apply_spec);
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index a4d7d9788..ff0974c2e 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -116,4 +116,17 @@ EXPERIMENTAL {
 	rte_swx_ctl_regarray_info_get;
 	rte_swx_pipeline_metarray_config;
 	rte_swx_pipeline_regarray_config;
+
+	#added in 21.08
+	rte_swx_pipeline_selector_config;
+	rte_swx_ctl_pipeline_selector_fprintf;
+	rte_swx_ctl_pipeline_selector_group_add;
+	rte_swx_ctl_pipeline_selector_group_delete;
+	rte_swx_ctl_pipeline_selector_group_member_add;
+	rte_swx_ctl_pipeline_selector_group_member_delete;
+	rte_swx_ctl_pipeline_selector_stats_read;
+	rte_swx_ctl_selector_info_get;
+	rte_swx_ctl_selector_field_info_get;
+	rte_swx_ctl_selector_group_id_field_info_get;
+	rte_swx_ctl_selector_member_id_field_info_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V3 4/5] examples/pipeline: add support for selector tables
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 2/5] table: add support for selector tables Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 3/5] pipeline: " Cristian Dumitrescu
@ 2021-07-02 22:46     ` Cristian Dumitrescu
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 5/5] examples/pipeline: add selector example Cristian Dumitrescu
  2021-07-09 21:37     ` [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands Thomas Monjalon
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:46 UTC (permalink / raw)
  To: dev; +Cc: Churchill Khangar

Add application-evel support for selector tables.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 563 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 563 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 30754e319..f67783c8f 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1368,6 +1368,467 @@ cmd_pipeline_table_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
 }
 
+static const char cmd_pipeline_selector_group_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group add\n";
+
+static void
+cmd_pipeline_selector_group_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group add");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_add(p->ctl,
+		selector_name,
+		&group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+	else
+		snprintf(out, out_size, "Group ID: %u\n", group_id);
+}
+
+static const char cmd_pipeline_selector_group_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group delete <group_id>\n";
+
+static void
+cmd_pipeline_selector_group_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 7) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group delete");
+		return;
+	}
+
+	if (parser_read_uint32(&group_id, tokens[6]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "group_id");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_delete(p->ctl,
+		selector_name,
+		group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+}
+
+#define GROUP_MEMBER_INFO_TOKENS_MAX 6
+
+static int
+token_is_comment(const char *token)
+{
+	if ((token[0] == '#') ||
+	    (token[0] == ';') ||
+	    ((token[0] == '/') && (token[1] == '/')))
+		return 1; /* TRUE. */
+
+	return 0; /* FALSE. */
+}
+
+static int
+pipeline_selector_group_member_read(const char *string,
+				      uint32_t *group_id,
+				      uint32_t *member_id,
+				      uint32_t *weight,
+				      int *is_blank_or_comment)
+{
+	char *token_array[GROUP_MEMBER_INFO_TOKENS_MAX], **tokens;
+	char *s0 = NULL, *s;
+	uint32_t n_tokens = 0, group_id_val, member_id_val, weight_val;
+	int blank_or_comment = 0;
+
+	/* Check input arguments. */
+	if (!string || !string[0])
+		goto error;
+
+	/* Memory allocation. */
+	s0 = strdup(string);
+	if (!s0)
+		goto error;
+
+	/* Parse the string into tokens. */
+	for (s = s0; ; ) {
+		char *token;
+
+		token = strtok_r(s, " \f\n\r\t\v", &s);
+		if (!token || token_is_comment(token))
+			break;
+
+		if (n_tokens > GROUP_MEMBER_INFO_TOKENS_MAX)
+			goto error;
+
+		token_array[n_tokens] = token;
+		n_tokens++;
+	}
+
+	if (!n_tokens) {
+		blank_or_comment = 1;
+		goto error;
+	}
+
+	tokens = token_array;
+
+	if (n_tokens < 4 ||
+		strcmp(tokens[0], "group") ||
+		strcmp(tokens[2], "member"))
+		goto error;
+
+	/*
+	 * Group ID.
+	 */
+	if (parser_read_uint32(&group_id_val, tokens[1]) != 0)
+		goto error;
+	*group_id = group_id_val;
+
+	/*
+	 * Member ID.
+	 */
+	if (parser_read_uint32(&member_id_val, tokens[3]) != 0)
+		goto error;
+	*member_id = member_id_val;
+
+	tokens += 4;
+	n_tokens -= 4;
+
+	/*
+	 * Weight.
+	 */
+	if (n_tokens && !strcmp(tokens[0], "weight")) {
+		if (n_tokens < 2)
+			goto error;
+
+		if (parser_read_uint32(&weight_val, tokens[1]) != 0)
+			goto error;
+		*weight = weight_val;
+
+		tokens += 2;
+		n_tokens -= 2;
+	}
+
+	if (n_tokens)
+		goto error;
+
+	free(s0);
+	return 0;
+
+error:
+	free(s0);
+	if (is_blank_or_comment)
+		*is_blank_or_comment = blank_or_comment;
+	return -EINVAL;
+}
+
+static int
+pipeline_selector_group_members_add(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_add(p,
+			selector_name,
+			group_id,
+			member_id,
+			weight);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member add <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member add");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_add(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_selector_group_members_delete(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_delete(p,
+			selector_name,
+			group_id,
+			member_id);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member delete <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member delete");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_delete(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static const char cmd_pipeline_selector_show_help[] =
+"pipeline <pipeline_name> selector <selector_name> show\n";
+
+static void
+cmd_pipeline_selector_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	int status;
+
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	selector_name = tokens[3];
+	status = rte_swx_ctl_pipeline_selector_fprintf(stdout,
+		p->ctl, selector_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2168,6 +2629,11 @@ cmd_help(char **tokens,
 			"\tpipeline table delete\n"
 			"\tpipeline table default\n"
 			"\tpipeline table show\n"
+			"\tpipeline selector group add\n"
+			"\tpipeline selector group delete\n"
+			"\tpipeline selector group member add\n"
+			"\tpipeline selector group member delete\n"
+			"\tpipeline selector show\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2266,6 +2732,57 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_show_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -2468,6 +2985,52 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "add") == 0)) {
+			cmd_pipeline_selector_group_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "delete") == 0)) {
+			cmd_pipeline_selector_group_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "add") == 0)) {
+			cmd_pipeline_selector_group_member_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "delete") == 0)) {
+			cmd_pipeline_selector_group_member_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_selector_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V3 5/5] examples/pipeline: add selector example
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
                       ` (2 preceding siblings ...)
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 4/5] examples/pipeline: add support for selector tables Cristian Dumitrescu
@ 2021-07-02 22:46     ` Cristian Dumitrescu
  2021-07-09 21:37     ` [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands Thomas Monjalon
  4 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-02 22:46 UTC (permalink / raw)
  To: dev

Added the files to illustrate the selector table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/examples/selector.cli  | 31 ++++++++
 examples/pipeline/examples/selector.spec | 95 ++++++++++++++++++++++++
 examples/pipeline/examples/selector.txt  |  4 +
 3 files changed, 130 insertions(+)
 create mode 100644 examples/pipeline/examples/selector.cli
 create mode 100644 examples/pipeline/examples/selector.spec
 create mode 100644 examples/pipeline/examples/selector.txt

diff --git a/examples/pipeline/examples/selector.cli b/examples/pipeline/examples/selector.cli
new file mode 100644
index 000000000..36f3ead54
--- /dev/null
+++ b/examples/pipeline/examples/selector.cli
@@ -0,0 +1,31 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/selector.spec
+
+pipeline PIPELINE0 selector s group add
+pipeline PIPELINE0 selector s group member add ./examples/pipeline/examples/selector.txt
+pipeline PIPELINE0 commit
+pipeline PIPELINE0 selector s show
+
+thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/selector.spec b/examples/pipeline/examples/selector.spec
new file mode 100644
index 000000000..72e49cb1d
--- /dev/null
+++ b/examples/pipeline/examples/selector.spec
@@ -0,0 +1,95 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; A selector table is made out of groups of weighted members, with a given member potentially part
+; of several groups. The select operation returns a member ID by first selecting a group based on an
+; input group ID and then selecting a member within that group by hashing one or several input
+; header or meta-data fields. It is very useful for implementing an Equal-Cost Multi-Path (ECMP) or
+; Weighted-Cost Multi-Path (WCMP) enabled FIB or a load balancer. It is part of the action selector
+; construct described by the P4 Portable Switch Architecture (PSA) specification.
+;
+; Normally, an action selector FIB is built with a routing table (the base table), a selector table
+; (the group table) and a next hop table (the member table). One of the routing table actions sets
+; up the group ID meta-data field used as the index into the group table, which produces the member
+; ID meta-data field, i.e. the next hop ID that is used as the index into the next hop table. The
+; next hop action prepares the output packet for being sent next hop in the network by prepending
+; one or several headers to the packet (Ethernet at the very least), decrementing the TTL and
+; recomputing the IPv4 checksum, etc. The selector allows for multiple next hops to be specified
+; for any given route as opposed to a single next hop per route; for every packet, its next hop is
+; picked out of the set of next hops defined for the route while preserving the packet ordering
+; within the flow, with the flow defined by the selector n-tuple fields.
+;
+; In this simple example, the base table and the member table are striped out in order to focus
+; exclusively on illustrating the selector table. The group_id is read from the destination MAC
+; address and the selector n-tuple is represented by the Protocol, the source IP address and the
+; destination IP address fields. The member_id produced by the selector table is used to identify
+; the output port which facilitates the testing of different member weights by simply comparing the
+; rates of output packets sent on different ports.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+	bit<32> group_id
+}
+
+metadata instanceof metadata_t
+
+//
+// Selectors.
+//
+selector s {
+	group_id m.group_id
+
+	selector {
+		h.ipv4.protocol
+		h.ipv4.src_addr
+		h.ipv4.dst_addr
+	}
+
+	member_id m.port_out
+
+	n_groups_max 64
+	n_members_per_group_max 16
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	mov m.group_id h.ethernet.dst_addr
+	table s
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
diff --git a/examples/pipeline/examples/selector.txt b/examples/pipeline/examples/selector.txt
new file mode 100644
index 000000000..b3c83c773
--- /dev/null
+++ b/examples/pipeline/examples/selector.txt
@@ -0,0 +1,4 @@
+group 0 member 0 weight 1
+group 0 member 1 weight 1
+group 0 member 2 weight 2
+group 0 member 3 weight 4
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands
  2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
                       ` (3 preceding siblings ...)
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 5/5] examples/pipeline: add selector example Cristian Dumitrescu
@ 2021-07-09 21:37     ` Thomas Monjalon
  2021-07-09 22:13       ` Thomas Monjalon
  4 siblings, 1 reply; 24+ messages in thread
From: Thomas Monjalon @ 2021-07-09 21:37 UTC (permalink / raw)
  To: Cristian Dumitrescu; +Cc: dev, Churchill Khangar

03/07/2021 00:46, Cristian Dumitrescu:
> From: Churchill Khangar <churchill.khangar@intel.com>
> 
> For more felxibility, the single monolithic table update command is
> split into table entry add, table entry delete, table default entry
> add, pipeline commit and pipeline abort.
> 
> Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>

Series applied, thanks.




^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands
  2021-07-09 21:37     ` [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands Thomas Monjalon
@ 2021-07-09 22:13       ` Thomas Monjalon
  2021-07-10  0:26         ` Dumitrescu, Cristian
  0 siblings, 1 reply; 24+ messages in thread
From: Thomas Monjalon @ 2021-07-09 22:13 UTC (permalink / raw)
  To: Cristian Dumitrescu; +Cc: dev, Churchill Khangar

09/07/2021 23:37, Thomas Monjalon:
> 03/07/2021 00:46, Cristian Dumitrescu:
> > From: Churchill Khangar <churchill.khangar@intel.com>
> > 
> > For more felxibility, the single monolithic table update command is
> > split into table entry add, table entry delete, table default entry
> > add, pipeline commit and pipeline abort.
> > 
> > Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> > Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> 
> Series applied, thanks.

Sorry, only first 2 patches are really kept,
because the patch 3 fails 32-bit compilation:

lib/pipeline/rte_swx_pipeline.c:9851:33: error:
array subscript ‘struct rte_swx_table_selector_params[0]’
is partly outside array bounds of ‘unsigned char[24]’ [-Werror=array-bounds]




^ permalink raw reply	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands
  2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 3/5] pipeline: " Cristian Dumitrescu
@ 2021-07-10  0:20       ` Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 2/5] table: add support for selector tables Cristian Dumitrescu
                           ` (3 more replies)
  0 siblings, 4 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-10  0:20 UTC (permalink / raw)
  To: dev, thomas; +Cc: Churchill Khangar

From: Churchill Khangar <churchill.khangar@intel.com>

For more felxibility, the single monolithic table update command is
split into table entry add, table entry delete, table default entry
add, pipeline commit and pipeline abort.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                   | 589 ++++++++++++++++------
 examples/pipeline/examples/vxlan.cli      |   3 +-
 examples/pipeline/examples/vxlan_pcap.cli |   3 +-
 3 files changed, 428 insertions(+), 167 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 215dd8e85..30754e319 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1038,25 +1038,76 @@ table_entry_free(struct rte_swx_table_entry *entry)
 	free(entry);
 }
 
-static const char cmd_pipeline_table_update_help[] =
-"pipeline <pipeline_name> table <table_name> update <file_name_add> "
-"<file_name_delete> <file_name_default>";
+#ifndef MAX_LINE_SIZE
+#define MAX_LINE_SIZE 2048
+#endif
+
+static int
+pipeline_table_entries_add(struct rte_swx_ctl_pipeline *p,
+			   const char *table_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_add(p,
+							      table_name,
+							      entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_table_add_help[] =
+"pipeline <pipeline_name> table <table_name> add <file_name>\n";
 
 static void
-cmd_pipeline_table_update(char **tokens,
-	uint32_t n_tokens,
-	char *out,
-	size_t out_size,
-	void *obj)
+cmd_pipeline_table_add(char **tokens,
+		       uint32_t n_tokens,
+		       char *out,
+		       size_t out_size,
+		       void *obj)
 {
 	struct pipeline *p;
-	char *pipeline_name, *table_name, *line = NULL;
-	char *file_name_add, *file_name_delete, *file_name_default;
-	FILE *file_add = NULL, *file_delete = NULL, *file_default = NULL;
-	uint32_t line_id;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
 	int status;
 
-	if (n_tokens != 8) {
+	if (n_tokens != 6) {
 		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
 		return;
 	}
@@ -1068,192 +1119,313 @@ cmd_pipeline_table_update(char **tokens,
 		return;
 	}
 
-	if (strcmp(tokens[2], "table") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "table");
+	table_name = tokens[3];
+
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_table_entries_add(p->ctl,
+					    table_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_entries_delete(struct rte_swx_ctl_pipeline *p,
+			      const char *table_name,
+			      FILE *file,
+			      uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_table_entry_delete(p,
+								 table_name,
+								 entry);
+		table_entry_free(entry);
+		if (status)
+			goto error;
+	}
+
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_delete_help[] =
+"pipeline <pipeline_name> table <table_name> delete <file_name>\n";
+
+static void
+cmd_pipeline_table_delete(char **tokens,
+			  uint32_t n_tokens,
+			  char *out,
+			  size_t out_size,
+			  void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
 	table_name = tokens[3];
 
-	if (strcmp(tokens[4], "update") != 0) {
-		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "update");
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
 		return;
 	}
 
-	file_name_add = tokens[5];
-	file_name_delete = tokens[6];
-	file_name_default = tokens[7];
+	status = pipeline_table_entries_delete(p->ctl,
+					       table_name,
+					       file,
+					       &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_table_default_entry_add(struct rte_swx_ctl_pipeline *p,
+				 const char *table_name,
+				 FILE *file,
+				 uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		struct rte_swx_table_entry *entry;
+		int is_blank_or_comment;
 
-	/* File open. */
-	if (strcmp(file_name_add, "none")) {
-		file_add = fopen(file_name_add, "r");
-		if (!file_add) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_add);
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		entry = rte_swx_ctl_pipeline_table_entry_read(p,
+							      table_name,
+							      line,
+							      &is_blank_or_comment);
+		if (!entry) {
+			if (is_blank_or_comment)
+				continue;
+
+			status = -EINVAL;
 			goto error;
 		}
-	}
 
-	if (strcmp(file_name_delete, "none")) {
-		file_delete = fopen(file_name_delete, "r");
-		if (!file_delete) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_delete);
+		status = rte_swx_ctl_pipeline_table_default_entry_add(p,
+								      table_name,
+								      entry);
+		table_entry_free(entry);
+		if (status)
 			goto error;
-		}
 	}
 
-	if (strcmp(file_name_default, "none")) {
-		file_default = fopen(file_name_default, "r");
-		if (!file_default) {
-			snprintf(out, out_size, "Cannot open file %s.\n",
-				file_name_default);
-			goto error;
-		}
+error:
+	*file_line_number = line_id;
+	free(line);
+	return status;
+}
+
+static const char cmd_pipeline_table_default_help[] =
+"pipeline <pipeline_name> table <table_name> default <file_name>\n";
+
+static void
+cmd_pipeline_table_default(char **tokens,
+			   uint32_t n_tokens,
+			   char *out,
+			   size_t out_size,
+			   void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
 	}
 
-	if (!file_add && !file_delete && !file_default) {
-		snprintf(out, out_size, "Nothing to be done.");
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
 		return;
 	}
 
-	/* Buffer allocation. */
-	line = malloc(2048);
-	if (!line) {
-		snprintf(out, out_size, MSG_OUT_OF_MEMORY);
-		goto error;
-	}
-
-	/* Add. */
-	if (file_add)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
-
-			if (fgets(line, 2048, file_add) == NULL)
-				break;
-
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
-
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_add, line_id);
-				goto error;
-			}
+	table_name = tokens[3];
 
-			status = rte_swx_ctl_pipeline_table_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_add, line_id);
-				goto error;
-			}
-		}
+	file_name = tokens[5];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
 
+	status = pipeline_table_default_entry_add(p->ctl,
+						  table_name,
+						  file,
+						  &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
 
-	/* Delete. */
-	if (file_delete)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	fclose(file);
+}
 
-			if (fgets(line, 2048, file_delete) == NULL)
-				break;
+static const char cmd_pipeline_table_show_help[] =
+"pipeline <pipeline_name> table <table_name> show\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_table_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *table_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_delete, line_id);
-				goto error;
-			}
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_entry_delete(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status)  {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_delete, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-	/* Default. */
-	if (file_default)
-		for (line_id = 1; ; line_id++) {
-			struct rte_swx_table_entry *entry;
-			int is_blank_or_comment;
+	table_name = tokens[3];
+	status = rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
+}
 
-			if (fgets(line, 2048, file_default) == NULL)
-				break;
+static const char cmd_pipeline_commit_help[] =
+"pipeline <pipeline_name> commit\n";
 
-			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
-				table_name,
-				line,
-				&is_blank_or_comment);
-			if (!entry) {
-				if (is_blank_or_comment)
-					continue;
+static void
+cmd_pipeline_commit(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
+	int status;
 
-				snprintf(out, out_size, MSG_FILE_ERR,
-					file_name_default, line_id);
-				goto error;
-			}
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
 
-			status = rte_swx_ctl_pipeline_table_default_entry_add(p->ctl,
-				table_name,
-				entry);
-			table_entry_free(entry);
-			if (status) {
-				snprintf(out, out_size,
-					"Invalid entry in file %s at line %u",
-					file_name_default, line_id);
-				goto error;
-			}
-		}
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
 	status = rte_swx_ctl_pipeline_commit(p->ctl, 1);
-	if (status) {
-		snprintf(out, out_size, "Commit failed.");
-		goto error;
-	}
+	if (status)
+		snprintf(out, out_size, "Commit failed. "
+			"Use \"commit\" to retry or \"abort\" to discard the pending work.\n");
+}
 
+static const char cmd_pipeline_abort_help[] =
+"pipeline <pipeline_name> abort\n";
 
-	rte_swx_ctl_pipeline_table_fprintf(stdout, p->ctl, table_name);
+static void
+cmd_pipeline_abort(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name;
 
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
-	return;
+	if (n_tokens != 3) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
 
-error:
 	rte_swx_ctl_pipeline_abort(p->ctl);
-	free(line);
-	if (file_add)
-		fclose(file_add);
-	if (file_delete)
-		fclose(file_delete);
-	if (file_default)
-		fclose(file_default);
 }
 
 static const char cmd_pipeline_regrd_help[] =
@@ -1992,7 +2164,12 @@ cmd_help(char **tokens,
 			"\tpipeline port in\n"
 			"\tpipeline port out\n"
 			"\tpipeline build\n"
-			"\tpipeline table update\n"
+			"\tpipeline table add\n"
+			"\tpipeline table delete\n"
+			"\tpipeline table default\n"
+			"\tpipeline table show\n"
+			"\tpipeline commit\n"
+			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
 			"\tpipeline regwr\n"
 			"\tpipeline meter profile add\n"
@@ -2056,9 +2233,52 @@ cmd_help(char **tokens,
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 3) &&
 		(strcmp(tokens[1], "table") == 0) &&
-		(strcmp(tokens[2], "update") == 0)) {
+		(strcmp(tokens[2], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "default") == 0)) {
 		snprintf(out, out_size, "\n%s\n",
-			cmd_pipeline_table_update_help);
+			cmd_pipeline_table_default_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "table") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_table_show_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "commit") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_commit_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 2) &&
+		(strcmp(tokens[1], "abort") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_abort_help);
 		return;
 	}
 
@@ -2216,9 +2436,48 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "add") == 0)) {
+			cmd_pipeline_table_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "delete") == 0)) {
+			cmd_pipeline_table_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "default") == 0)) {
+			cmd_pipeline_table_default(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "table") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_table_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 3) &&
+			(strcmp(tokens[2], "commit") == 0)) {
+			cmd_pipeline_commit(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
-			(strcmp(tokens[2], "table") == 0)) {
-			cmd_pipeline_table_update(tokens, n_tokens, out,
+			(strcmp(tokens[2], "abort") == 0)) {
+			cmd_pipeline_abort(tokens, n_tokens, out,
 				out_size, obj);
 			return;
 		}
diff --git a/examples/pipeline/examples/vxlan.cli b/examples/pipeline/examples/vxlan.cli
index 7bf4a5757..a3bde6a9f 100644
--- a/examples/pipeline/examples/vxlan.cli
+++ b/examples/pipeline/examples/vxlan.cli
@@ -22,6 +22,7 @@ pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/pipeline/examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/vxlan_pcap.cli b/examples/pipeline/examples/vxlan_pcap.cli
index 1636ba080..3cc9a94af 100644
--- a/examples/pipeline/examples/vxlan_pcap.cli
+++ b/examples/pipeline/examples/vxlan_pcap.cli
@@ -17,6 +17,7 @@ pipeline PIPELINE0 port out 3 sink none
 pipeline PIPELINE0 port out 4 sink none
 
 pipeline PIPELINE0 build ./examples/vxlan.spec
-pipeline PIPELINE0 table vxlan_table update ./examples/vxlan_table.txt none none
+pipeline PIPELINE0 table vxlan_table add ./examples/vxlan_table.txt
+pipeline PIPELINE0 commit
 
 thread 1 pipeline PIPELINE0 enable
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V4 2/5] table: add support for selector tables
  2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
@ 2021-07-10  0:20         ` Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 3/5] pipeline: " Cristian Dumitrescu
                           ` (2 subsequent siblings)
  3 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-10  0:20 UTC (permalink / raw)
  To: dev, thomas

A selector table is made up of groups of weighted members, with a
given member potentially part of several groups. The select operation
returns a member ID by first selecting a group based on an input group
ID and then selecting a member within that group based on hashing one
or several input header/meta-data fields. It is very useful for
implementing an ECMP/WCMP-enabled FIB or a load balancer. It is part
of the action selector described by the P4 Portable Switch
Architecture (PSA) specification.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/table/meson.build              |   2 +
 lib/table/rte_swx_table_selector.c | 581 +++++++++++++++++++++++++++++
 lib/table/rte_swx_table_selector.h | 203 ++++++++++
 lib/table/version.map              |   8 +
 4 files changed, 794 insertions(+)
 create mode 100644 lib/table/rte_swx_table_selector.c
 create mode 100644 lib/table/rte_swx_table_selector.h

diff --git a/lib/table/meson.build b/lib/table/meson.build
index b7b70b805..16e55f086 100644
--- a/lib/table/meson.build
+++ b/lib/table/meson.build
@@ -3,6 +3,7 @@
 
 sources = files(
         'rte_swx_table_em.c',
+	'rte_swx_table_selector.c',
         'rte_swx_table_wm.c',
         'rte_table_acl.c',
         'rte_table_array.c',
@@ -20,6 +21,7 @@ headers = files(
         'rte_lru.h',
         'rte_swx_table.h',
         'rte_swx_table_em.h',
+	'rte_swx_table_selector.h',
         'rte_swx_table_wm.h',
         'rte_table.h',
         'rte_table_acl.h',
diff --git a/lib/table/rte_swx_table_selector.c b/lib/table/rte_swx_table_selector.c
new file mode 100644
index 000000000..541ebc221
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.c
@@ -0,0 +1,581 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_prefetch.h>
+
+#include "rte_swx_table_selector.h"
+
+#ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
+
+#include <rte_malloc.h>
+
+static void *
+env_calloc(size_t size, size_t alignment, int numa_node)
+{
+	return rte_zmalloc_socket(NULL, size, alignment, numa_node);
+}
+
+static void
+env_free(void *start, size_t size __rte_unused)
+{
+	rte_free(start);
+}
+
+#else
+
+#include <numa.h>
+
+static void *
+env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
+{
+	void *start;
+
+	if (numa_available() == -1)
+		return NULL;
+
+	start = numa_alloc_onnode(size, numa_node);
+	if (!start)
+		return NULL;
+
+	memset(start, 0, size);
+	return start;
+}
+
+static void
+env_free(void *start, size_t size)
+{
+	if ((numa_available() == -1) || !start)
+		return;
+
+	numa_free(start, size);
+}
+
+#endif
+
+#if defined(RTE_ARCH_X86_64)
+
+#include <x86intrin.h>
+
+#define crc32_u64(crc, v) _mm_crc32_u64(crc, v)
+
+#else
+
+static inline uint64_t
+crc32_u64_generic(uint64_t crc, uint64_t value)
+{
+	int i;
+
+	crc = (crc & 0xFFFFFFFFLLU) ^ value;
+	for (i = 63; i >= 0; i--) {
+		uint64_t mask;
+
+		mask = -(crc & 1LLU);
+		crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask);
+	}
+
+	return crc;
+}
+
+#define crc32_u64(crc, v) crc32_u64_generic(crc, v)
+
+#endif
+
+/* Key size needs to be one of: 8, 16, 32 or 64. */
+static inline uint32_t
+hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = key_mask;
+	uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5;
+
+	switch (key_size) {
+	case 8:
+		crc0 = crc32_u64(seed, k[0] & m[0]);
+		return crc0;
+
+	case 16:
+		k0 = k[0] & m[0];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 32:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = k2 >> 32;
+
+		crc0 = crc32_u64(crc0, crc1);
+		crc1 = crc32_u64(crc2, crc3);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	case 64:
+		k0 = k[0] & m[0];
+		k2 = k[2] & m[2];
+		k5 = k[5] & m[5];
+
+		crc0 = crc32_u64(k0, seed);
+		crc1 = crc32_u64(k0 >> 32, k[1] & m[1]);
+
+		crc2 = crc32_u64(k2, k[3] & m[3]);
+		crc3 = crc32_u64(k2 >> 32, k[4] & m[4]);
+
+		crc4 = crc32_u64(k5, k[6] & m[6]);
+		crc5 = crc32_u64(k5 >> 32, k[7] & m[7]);
+
+		crc0 = crc32_u64(crc0, (crc1 << 32) ^ crc2);
+		crc1 = crc32_u64(crc3, (crc4 << 32) ^ crc5);
+
+		crc0 ^= crc1;
+
+		return crc0;
+
+	default:
+		crc0 = 0;
+		return crc0;
+	}
+}
+
+struct group_member_info {
+	uint32_t member_id;
+	uint32_t member_weight;
+	uint32_t member_weight_normalized;
+	uint32_t count;
+};
+
+struct table {
+	/* Input parameters */
+	struct rte_swx_table_selector_params params;
+
+	/* Internal. */
+	uint32_t *group_table;
+	uint64_t group_table_size;
+	struct group_member_info *members;
+	uint32_t n_members_per_group_max_log2;
+};
+
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max)
+{
+	uint64_t group_table_size, members_size;
+
+	group_table_size = n_groups_max * n_members_per_group_max * sizeof(uint32_t);
+
+	members_size = n_members_per_group_max * sizeof(struct group_member_info);
+
+	return sizeof(struct table) + group_table_size + members_size;
+}
+
+void
+rte_swx_table_selector_free(void *table)
+{
+	struct table *t = table;
+
+	if (!t)
+		return;
+
+	free(t->members);
+
+	env_free(t->group_table, t->group_table_size);
+
+	free(t->params.selector_mask);
+
+	free(t);
+}
+
+static int
+table_create_check(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return -1;
+
+	if (!params->selector_size ||
+	    (params->selector_size > 64) ||
+	    !params->n_groups_max ||
+	    (params->n_groups_max > 1U << 31) ||
+	    !params->n_members_per_group_max ||
+	    (params->n_members_per_group_max > 1U << 31))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+table_params_copy(struct table *t, struct rte_swx_table_selector_params *params)
+{
+	uint32_t selector_size, i;
+
+	selector_size = rte_align32pow2(params->selector_size);
+	if (selector_size < 8)
+		selector_size = 8;
+
+	memcpy(&t->params, params, sizeof(struct rte_swx_table_selector_params));
+	t->params.selector_size = selector_size;
+	t->params.selector_mask = NULL;
+	t->params.n_groups_max = rte_align32pow2(params->n_groups_max);
+	t->params.n_members_per_group_max = rte_align32pow2(params->n_members_per_group_max);
+
+	for (i = 0; i < 32; i++)
+		if (params->n_members_per_group_max == 1U << i)
+			t->n_members_per_group_max_log2 = i;
+
+	/* t->params.selector_mask */
+	t->params.selector_mask = calloc(selector_size, sizeof(uint8_t));
+	if (!t->params.selector_mask)
+		goto error;
+
+	if (params->selector_mask)
+		memcpy(t->params.selector_mask, params->selector_mask, params->selector_size);
+	else
+		memset(t->params.selector_mask, 0xFF, params->selector_size);
+
+	return 0;
+
+error:
+	free(t->params.selector_mask);
+	t->params.selector_mask = NULL;
+
+	return -ENOMEM;
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group);
+
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node)
+{
+	struct table *t = NULL;
+	uint32_t group_size, i;
+	int status;
+
+	/* Check input arguments. */
+	status = table_create_check(params);
+	if (status)
+		goto error;
+
+	/* Table object. */
+	t = calloc(1, sizeof(struct table));
+	if (!t)
+		goto error;
+
+	/* Parameter copy. */
+	status = table_params_copy(t, params);
+	if (status)
+		goto error;
+
+	/* Group. */
+	group_size = params->n_members_per_group_max * sizeof(uint32_t);
+	t->group_table_size = params->n_groups_max * group_size;
+
+	t->group_table = env_calloc(t->group_table_size, RTE_CACHE_LINE_SIZE, numa_node);
+	if (!t->group_table)
+		goto error;
+
+	t->members = calloc(params->n_members_per_group_max, sizeof(struct group_member_info));
+	if (!t->members)
+		goto error;
+
+	if (groups)
+		for (i = 0; i < params->n_groups_max; i++)
+			if (groups[i]) {
+				status = group_set(t, i, groups[i]);
+				if (status)
+					goto error;
+			}
+
+	return t;
+
+error:
+	rte_swx_table_selector_free(t);
+	return NULL;
+}
+
+
+static int
+group_check(struct table *t, struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct rte_swx_table_selector_member *e;
+		uint32_t n = 0;
+
+		/* Check group size. */
+		if (n_members >= t->params.n_members_per_group_max)
+			return -ENOSPC;
+
+		/* Check attributes of the current group member. */
+		if (elem->member_id >= t->params.n_members_per_group_max ||
+		    !elem->member_weight)
+			return -ENOSPC;
+
+		/* Check against duplicate member IDs. */
+		TAILQ_FOREACH(e, &group->members, node)
+			if (e->member_id == elem->member_id)
+				n++;
+
+		if (n != 1)
+			return -EINVAL;
+
+		/* Update group size. */
+		n_members++;
+	}
+
+	return 0;
+}
+
+static uint32_t
+members_read(struct group_member_info *members,
+	     struct rte_swx_table_selector_group *group)
+{
+	struct rte_swx_table_selector_member *elem;
+	uint32_t n_members = 0;
+
+	if (!group)
+		return 0;
+
+	TAILQ_FOREACH(elem, &group->members, node) {
+		struct group_member_info *m = &members[n_members];
+
+		memset(m, 0, sizeof(struct group_member_info));
+
+		m->member_id = elem->member_id;
+		m->member_weight = elem->member_weight;
+		m->member_weight_normalized = elem->member_weight;
+
+		n_members++;
+	}
+
+	return n_members;
+}
+
+static uint32_t
+members_min_weight_find(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t min = UINT32_MAX, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight < min)
+			min = m->member_weight;
+	}
+
+	return min;
+}
+
+static uint32_t
+members_weight_divisor_check(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		if (m->member_weight_normalized % divisor)
+			return 0; /* FALSE. */
+	}
+
+	return 1; /* TRUE. */
+}
+
+static void
+members_weight_divisor_apply(struct group_member_info *members,
+			     uint32_t n_members,
+			     uint32_t divisor)
+{
+	uint32_t i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->member_weight_normalized /= divisor;
+	}
+}
+
+static uint32_t
+members_weight_sum(struct group_member_info *members, uint32_t n_members)
+{
+	uint32_t result = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		result += m->member_weight_normalized;
+	}
+
+	return result;
+}
+
+static void
+members_weight_scale(struct group_member_info *members,
+		     uint32_t n_members,
+		     uint32_t n_members_per_group_max,
+		     uint32_t weight_sum)
+{
+	uint32_t multiplier, remainder, i;
+
+	multiplier = n_members_per_group_max / weight_sum;
+	remainder = n_members_per_group_max % weight_sum;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+
+		m->count = m->member_weight_normalized * multiplier;
+	}
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t min;
+
+		min = m->member_weight_normalized;
+		if (remainder < m->member_weight_normalized)
+			min = remainder;
+
+		m->count += min;
+		remainder -= min;
+		if (!remainder)
+			break;
+	}
+}
+
+static void
+members_write(struct group_member_info *members,
+	      uint32_t n_members,
+	      uint32_t *group_table)
+{
+	uint32_t pos = 0, i;
+
+	for (i = 0; i < n_members; i++) {
+		struct group_member_info *m = &members[i];
+		uint32_t j;
+
+		for (j = 0; j < m->count; j++)
+			group_table[pos++] = m->member_id;
+	}
+}
+
+static int
+group_set(struct table *t,
+	  uint32_t group_id,
+	  struct rte_swx_table_selector_group *group)
+{
+	uint32_t *gt = &t->group_table[group_id * t->params.n_members_per_group_max];
+	struct group_member_info *members = t->members;
+	uint32_t n_members, weight_min, weight_sum, divisor;
+	int status = 0;
+
+	/* Check input arguments. */
+	if (group_id >= t->params.n_groups_max)
+		return -EINVAL;
+
+	status = group_check(t, group);
+	if (status)
+		return status;
+
+	/* Read group members. */
+	n_members = members_read(members, group);
+
+	if (!n_members) {
+		memset(gt, 0, t->params.n_members_per_group_max * sizeof(uint32_t));
+
+		return 0;
+	}
+
+	/* Normalize weights. */
+	weight_min = members_min_weight_find(members, n_members);
+
+	for (divisor = 2; divisor <= weight_min; divisor++)
+		if (members_weight_divisor_check(members, n_members, divisor))
+			members_weight_divisor_apply(members, n_members, divisor);
+
+	/* Scale weights. */
+	weight_sum = members_weight_sum(members, n_members);
+	if (weight_sum > t->params.n_members_per_group_max)
+		return -ENOSPC;
+
+	members_weight_scale(members, n_members, t->params.n_members_per_group_max, weight_sum);
+
+	/* Write group members to the group table. */
+	members_write(members, n_members, gt);
+
+	return 0;
+}
+
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group)
+{
+	struct table *t = table;
+
+	return group_set(t, group_id, group);
+}
+
+struct mailbox {
+
+};
+
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void)
+{
+	return sizeof(struct mailbox);
+}
+
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox __rte_unused,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer)
+{
+	struct table *t = table;
+	uint32_t *group_id_ptr, *member_id_ptr, group_id, member_id, selector, group_member_index;
+
+	group_id_ptr = (uint32_t *)&(*group_id_buffer)[t->params.group_id_offset];
+
+	member_id_ptr = (uint32_t *)&(*member_id_buffer)[t->params.member_id_offset];
+
+	group_id = *group_id_ptr & (t->params.n_groups_max - 1);
+
+	selector = hash(&(*selector_buffer)[t->params.selector_offset],
+			t->params.selector_mask,
+			t->params.selector_size,
+			0);
+
+	group_member_index = selector & (t->params.n_members_per_group_max - 1);
+
+	member_id = t->group_table[(group_id << t->n_members_per_group_max_log2) +
+				   group_member_index];
+
+	*member_id_ptr = member_id;
+
+	return 1;
+}
diff --git a/lib/table/rte_swx_table_selector.h b/lib/table/rte_swx_table_selector.h
new file mode 100644
index 000000000..71b6a7481
--- /dev/null
+++ b/lib/table/rte_swx_table_selector.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+#ifndef __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+#define __INCLUDE_RTE_SWX_TABLE_SELECTOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * RTE SWX Selector Table
+ *
+ * Selector table interface.
+ */
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#include <rte_compat.h>
+
+#include "rte_swx_table.h"
+
+/** Selector table creation parameters. */
+struct rte_swx_table_selector_params {
+	/** Group ID offset. */
+	uint32_t group_id_offset;
+
+	/** Selector size in bytes. Must be non-zero. */
+	uint32_t selector_size;
+
+	/** Offset of the first byte of the selector within the selector buffer. */
+	uint32_t selector_offset;
+
+	/** Mask of *selector_size* bytes logically laid over the bytes at positions
+	 * selector_offset* .. (*selector_offset* + *selector_size* - 1) of the selector buffer in
+	 * order to specify which bits from the selector buffer are part of the selector and which
+	 * ones are not. A bit value of 1 in the *selector_mask* means the respective bit in the
+	 * selector buffer is part of the selector, while a bit value of 0 means the opposite. A
+	 * NULL value means that all the bits are part of the selector, i.e. the *selector_mask*
+	 * is an all-ones mask.
+	 */
+	uint8_t *selector_mask;
+
+	/** Member ID offset. */
+	uint32_t member_id_offset;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/** Group member parameters. */
+struct rte_swx_table_selector_member {
+	/** Linked list connectivity. */
+	TAILQ_ENTRY(rte_swx_table_selector_member) node;
+
+	/** Member ID. */
+	uint32_t member_id;
+
+	/** Member weight. */
+	uint32_t member_weight;
+};
+
+/** List of group members. */
+TAILQ_HEAD(rte_swx_table_selector_member_list, rte_swx_table_selector_member);
+
+/** Group parameters. */
+struct rte_swx_table_selector_group {
+	/** List of group members. */
+	struct rte_swx_table_selector_member_list members;
+};
+
+/**
+ * Selector table memory footprint get
+ *
+ * @param[in] n_groups_max
+ *   Maximum number of groups. Must be non-zero.
+ * @param[in] n_members_per_group_max
+ *   Maximum number of members per group. Must be non-zero.
+ * @return
+ *   Selector table memory footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_footprint_get(uint32_t n_groups_max, uint32_t n_members_per_group_max);
+
+/**
+ * Selector table mailbox size get
+ *
+ * The mailbox is used to store the context of a select operation that is in
+ * progress and it is passed as a parameter to the select operation. This allows
+ * for multiple concurrent select operations into the same table.
+ *
+ * @return
+ *   Selector table mailbox footprint in bytes.
+ */
+__rte_experimental
+uint64_t
+rte_swx_table_selector_mailbox_size_get(void);
+
+/**
+ * Selector table create
+ *
+ * @param[in] params
+ *   Selector table creation parameters.
+ * @param[in] groups
+ *   Groups to be added to the table at creation time. When NULL, it signifies that all groups are
+ *   invalid, otherwise it points to a pre-allocated array of size *n_groups_max*, where a NULL
+ *   element indicates that the associated group is invalid.
+ * @param[in] numa_node
+ *   Non-Uniform Memory Access (NUMA) node.
+ * @return
+ *   Table handle, on success, or NULL, on error.
+ */
+__rte_experimental
+void *
+rte_swx_table_selector_create(struct rte_swx_table_selector_params *params,
+			      struct rte_swx_table_selector_group **groups,
+			      int numa_node);
+
+/**
+ * Group set
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] group_id
+ *   Group ID.
+ * @param[in] group
+ *   Group parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument(s);
+ *   -ENOSPC: Too many group members.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_group_set(void *table,
+				 uint32_t group_id,
+				 struct rte_swx_table_selector_group *group);
+
+/**
+ * Selector table select
+ *
+ * This operation selects a member from the given group based on a hasing scheme.
+ *
+ * Multiple invocations of this function may be required in order to complete a single select
+ * operation for a given table and a given group ID. The completion of the operation is flagged by
+ * a return value of 1; in case of a return value of 0, the function must be invoked again with
+ * exactly the same arguments.
+ *
+ * The mailbox argument is used to store the context of each on-going  operation. The mailbox
+ * mechanism allows for multiple concurrent select operations into the same table.
+ *
+ * The typical reason an implementation may choose to split the operation into multiple steps is to
+ * hide the latency of the inherrent memory read operations: before a read operation with the
+ * source data likely not in the CPU cache, the source data prefetch is issued and the operation is
+ * postponed in favor of some other unrelated work, which the CPU executes in parallel with the
+ * source data being fetched into the CPU cache; later on, the operation is resumed, this time with
+ * the source data likely to be read from the CPU cache with no CPU pipeline stall, which
+ * significantly improves the operation performance.
+ *
+ * @param[in] table
+ *   Selector table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] group_id_buffer
+ *   Buffer where the input group ID is located at offset *group_id_offset*.
+ * @param[in] selector_buffer
+ *   Buffer where the key to select a member within the identified group is located starting from
+ *   offset *selector_offset*. Its size must be equal to the table *selector_size*.
+ * @param[in] member_id_buffer
+ *   Buffer where the output member ID is to be placed at offset *member_id_offset*.
+ * @return
+ *   0 when the operation is not yet completed, and 1 when the operation is complete. No other
+ *   return values are allowed.
+ */
+__rte_experimental
+int
+rte_swx_table_selector_select(void *table,
+			      void *mailbox,
+			      uint8_t **group_id_buffer,
+			      uint8_t **selector_buffer,
+			      uint8_t **member_id_buffer);
+
+/**
+ * Selector table free
+ *
+ * @param[in] table
+ *   Selector table handle.
+ */
+__rte_experimental
+void
+rte_swx_table_selector_free(void *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/table/version.map b/lib/table/version.map
index eb0291ac4..29301480c 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -28,4 +28,12 @@ EXPERIMENTAL {
 
 	# added in 21.05
 	rte_swx_table_wildcard_match_ops;
+
+	# added in 21.08
+	rte_swx_table_selector_create;
+	rte_swx_table_selector_footprint_get;
+	rte_swx_table_selector_free;
+	rte_swx_table_selector_group_set;
+	rte_swx_table_selector_mailbox_size_get;
+	rte_swx_table_selector_select;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V4 3/5] pipeline: add support for selector tables
  2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 2/5] table: add support for selector tables Cristian Dumitrescu
@ 2021-07-10  0:20         ` Cristian Dumitrescu
  2021-07-10  6:27           ` Thomas Monjalon
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 4/5] examples/pipeline: " Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 5/5] examples/pipeline: add selector example Cristian Dumitrescu
  3 siblings, 1 reply; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-10  0:20 UTC (permalink / raw)
  To: dev, thomas

Add pipeline-level support for selector tables,

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Change log:

V3 -> V4: Fixed proper name of allocated structure
(rte_swx_table_selector_params instead of rte_swx_pipeline_selector_params).

 lib/pipeline/rte_swx_ctl.c           | 700 ++++++++++++++++++++++++-
 lib/pipeline/rte_swx_ctl.h           | 253 +++++++++
 lib/pipeline/rte_swx_pipeline.c      | 748 ++++++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h      |  51 ++
 lib/pipeline/rte_swx_pipeline_spec.c | 354 ++++++++++++-
 lib/pipeline/version.map             |  13 +
 6 files changed, 2034 insertions(+), 85 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c
index 5d04e750f..8cabce2b9 100644
--- a/lib/pipeline/rte_swx_ctl.c
+++ b/lib/pipeline/rte_swx_ctl.c
@@ -10,6 +10,8 @@
 #include <rte_common.h>
 #include <rte_byteorder.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_ctl.h"
 
 #define CHECK(condition, err_code)                                             \
@@ -89,11 +91,44 @@ struct table {
 	uint32_t n_delete;
 };
 
+struct selector {
+	/* Selector table info. */
+	struct rte_swx_ctl_selector_info info;
+
+	/* group_id field. */
+	struct rte_swx_ctl_table_match_field_info group_id_field;
+
+	/* selector fields. */
+	struct rte_swx_ctl_table_match_field_info *selector_fields;
+
+	/* member_id field. */
+	struct rte_swx_ctl_table_match_field_info member_id_field;
+
+	/* Current selector table. Array of info.n_groups_max elements.*/
+	struct rte_swx_table_selector_group **groups;
+
+	/* Pending selector table subject to the next commit. Array of info.n_groups_max elements.
+	 */
+	struct rte_swx_table_selector_group **pending_groups;
+
+	/* Valid flag per group. Array of n_groups_max elements. */
+	int *groups_added;
+
+	/* Pending delete flag per group. Group deletion is subject to the next commit. Array of
+	 * info.n_groups_max elements.
+	 */
+	int *groups_pending_delete;
+
+	/* Params. */
+	struct rte_swx_table_selector_params params;
+};
+
 struct rte_swx_ctl_pipeline {
 	struct rte_swx_ctl_pipeline_info info;
 	struct rte_swx_pipeline *p;
 	struct action *actions;
 	struct table *tables;
+	struct selector *selectors;
 	struct rte_swx_table_state *ts;
 	struct rte_swx_table_state *ts_next;
 	int numa_node;
@@ -709,6 +744,209 @@ table_free(struct rte_swx_ctl_pipeline *ctl)
 	ctl->tables = NULL;
 }
 
+static void
+selector_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->groups[group_id] = NULL;
+}
+
+static void
+selector_pending_group_members_free(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+
+	if (!group)
+		return;
+
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	free(group);
+	s->pending_groups[group_id] = NULL;
+}
+
+static int
+selector_group_duplicate_to_pending(struct selector *s, uint32_t group_id)
+{
+	struct rte_swx_table_selector_group *g, *gp;
+	struct rte_swx_table_selector_member *m;
+
+	selector_pending_group_members_free(s, group_id);
+
+	g = s->groups[group_id];
+	gp = s->pending_groups[group_id];
+
+	if (!gp) {
+		gp = calloc(1, sizeof(struct rte_swx_table_selector_group));
+		if (!gp)
+			goto error;
+
+		TAILQ_INIT(&gp->members);
+
+		s->pending_groups[group_id] = gp;
+	}
+
+	if (!g)
+		return 0;
+
+	TAILQ_FOREACH(m, &g->members, node) {
+		struct rte_swx_table_selector_member *mp;
+
+		mp = calloc(1, sizeof(struct rte_swx_table_selector_member));
+		if (!mp)
+			goto error;
+
+		memcpy(mp, m, sizeof(struct rte_swx_table_selector_member));
+
+		TAILQ_INSERT_TAIL(&gp->members, mp, node);
+	}
+
+	return 0;
+
+error:
+	selector_pending_group_members_free(s, group_id);
+	return -ENOMEM;
+}
+
+static void
+selector_free(struct rte_swx_ctl_pipeline *ctl)
+{
+	uint32_t i;
+
+	if (ctl->selectors)
+		return;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t i;
+
+		/* selector_fields. */
+		free(s->selector_fields);
+
+		/* groups. */
+		if (s->groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_group_members_free(s, i);
+
+		free(s->groups);
+
+		/* pending_groups. */
+		if (s->pending_groups)
+			for (i = 0; i < s->info.n_groups_max; i++)
+				selector_pending_group_members_free(s, i);
+
+		free(s->pending_groups);
+
+		/* groups_added. */
+		free(s->groups_added);
+
+		/* groups_pending_delete. */
+		free(s->groups_pending_delete);
+
+		/* params. */
+		free(s->params.selector_mask);
+	}
+
+	free(ctl->selectors);
+	ctl->selectors = NULL;
+}
+
+static struct selector *
+selector_find(struct rte_swx_ctl_pipeline *ctl, const char *selector_name)
+{
+	uint32_t i;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+
+		if (!strcmp(selector_name, s->info.name))
+			return s;
+	}
+
+	return NULL;
+}
+
+static int
+selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_ctl_table_match_field_info *first = NULL, *last = NULL;
+	uint8_t *selector_mask = NULL;
+	uint32_t selector_size = 0, selector_offset = 0, i;
+
+	/* Find first (smallest offset) and last (biggest offset) match fields. */
+	first = &s->selector_fields[0];
+	last = &s->selector_fields[0];
+
+	for (i = 1; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* selector_offset. */
+	selector_offset = first->offset / 8;
+
+	/* selector_size. */
+	selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* selector_mask. */
+	selector_mask = calloc(1, selector_size);
+	if (!selector_mask)
+		return -ENOMEM;
+
+	for (i = 0; i < s->info.n_selector_fields; i++) {
+		struct rte_swx_ctl_table_match_field_info *f = &s->selector_fields[i];
+		uint32_t start;
+		size_t size;
+
+		start = (f->offset - first->offset) / 8;
+		size = f->n_bits / 8;
+
+		memset(&selector_mask[start], 0xFF, size);
+	}
+
+	/* Fill in. */
+	s->params.group_id_offset = s->group_id_field.offset / 8;
+	s->params.selector_size = selector_size;
+	s->params.selector_offset = selector_offset;
+	s->params.selector_mask = selector_mask;
+	s->params.member_id_offset = s->member_id_field.offset / 8;
+	s->params.n_groups_max = s->info.n_groups_max;
+	s->params.n_members_per_group_max = s->info.n_members_per_group_max;
+
+	return 0;
+}
+
 static void
 table_state_free(struct rte_swx_ctl_pipeline *ctl)
 {
@@ -730,6 +968,15 @@ table_state_free(struct rte_swx_ctl_pipeline *ctl)
 			table->ops.free(ts->obj);
 	}
 
+	/* For each selector table, free its table state. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct rte_swx_table_state *ts = &ctl->ts_next[i];
+
+		/* Table object. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
 	free(ctl->ts_next);
 	ctl->ts_next = NULL;
 }
@@ -740,13 +987,14 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 	int status = 0;
 	uint32_t i;
 
-	ctl->ts_next = calloc(ctl->info.n_tables,
+	ctl->ts_next = calloc(ctl->info.n_tables + ctl->info.n_selectors,
 			      sizeof(struct rte_swx_table_state));
 	if (!ctl->ts_next) {
 		status = -ENOMEM;
 		goto error;
 	}
 
+	/* Tables. */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		struct table *table = &ctl->tables[i];
 		struct rte_swx_table_state *ts = &ctl->ts[i];
@@ -782,6 +1030,19 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl)
 		ts_next->default_action_id = ts->default_action_id;
 	}
 
+	/* Selector tables. */
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + i];
+
+		/* Table object. */
+		ts_next->obj = rte_swx_table_selector_create(&s->params, NULL, ctl->numa_node);
+		if (!ts_next->obj) {
+			status = -ENODEV;
+			goto error;
+		}
+	}
+
 	return 0;
 
 error:
@@ -799,6 +1060,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl)
 
 	table_state_free(ctl);
 
+	selector_free(ctl);
+
 	table_free(ctl);
 
 	free(ctl);
@@ -940,6 +1203,77 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p)
 			goto error;
 	}
 
+	/* selector tables. */
+	ctl->selectors = calloc(ctl->info.n_selectors, sizeof(struct selector));
+	if (!ctl->selectors)
+		goto error;
+
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		struct selector *s = &ctl->selectors[i];
+		uint32_t j;
+
+		/* info. */
+		status = rte_swx_ctl_selector_info_get(p, i, &s->info);
+		if (status)
+			goto error;
+
+		/* group_id field. */
+		status = rte_swx_ctl_selector_group_id_field_info_get(p,
+			i,
+			&s->group_id_field);
+		if (status)
+			goto error;
+
+		/* selector fields. */
+		s->selector_fields = calloc(s->info.n_selector_fields,
+			sizeof(struct rte_swx_ctl_table_match_field_info));
+		if (!s->selector_fields)
+			goto error;
+
+		for (j = 0; j < s->info.n_selector_fields; j++) {
+			status = rte_swx_ctl_selector_field_info_get(p,
+				i,
+				j,
+				&s->selector_fields[j]);
+			if (status)
+				goto error;
+		}
+
+		/* member_id field. */
+		status = rte_swx_ctl_selector_member_id_field_info_get(p,
+			i,
+			&s->member_id_field);
+		if (status)
+			goto error;
+
+		/* groups. */
+		s->groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->groups)
+			goto error;
+
+		/* pending_groups. */
+		s->pending_groups = calloc(s->info.n_groups_max,
+			sizeof(struct rte_swx_table_selector_group *));
+		if (!s->pending_groups)
+			goto error;
+
+		/* groups_added. */
+		s->groups_added = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_added)
+			goto error;
+
+		/* groups_pending_delete. */
+		s->groups_pending_delete = calloc(s->info.n_groups_max, sizeof(int));
+		if (!s->groups_pending_delete)
+			goto error;
+
+		/* params. */
+		status = selector_params_get(ctl, i);
+		if (status)
+			goto error;
+	}
+
 	/* ts. */
 	status = rte_swx_pipeline_table_state_get(p, &ctl->ts);
 	if (status)
@@ -1499,6 +1833,295 @@ table_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id)
 	table_pending_default_free(table);
 }
 
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id)
+{
+	struct selector *s;
+	uint32_t i;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0] || !group_id)
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Find an unused group. */
+	for (i = 0; i < s->info.n_groups_max; i++)
+		if (!s->groups_added[i]) {
+			*group_id = i;
+			s->groups_added[i] = 1;
+			return 0;
+		}
+
+	return -ENOSPC;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id])
+		return -EINVAL;
+
+	/* Check if this group is already scheduled for deletion. */
+	if (s->groups_pending_delete[group_id])
+		return 0;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Schedule removal of all the members from the current group. */
+	for ( ; ; ) {
+		struct rte_swx_table_selector_member *m;
+
+		m = TAILQ_FIRST(&group->members);
+		if (!m)
+			break;
+
+		TAILQ_REMOVE(&group->members, m, node);
+		free(m);
+	}
+
+	/* Schedule the group for deletion. */
+	s->groups_pending_delete[group_id] = 1;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	if (!member_weight)
+		return rte_swx_ctl_pipeline_selector_group_member_delete(ctl,
+									 selector_name,
+									 group_id,
+									 member_id);
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	   (group_id >= s->info.n_groups_max) ||
+	   !s->groups_added[group_id] ||
+	   s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* If this member is already in this group, then simply update its weight and return. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			m->member_weight = member_weight;
+			return 0;
+		}
+
+	/* Add new member to this group. */
+	m = calloc(1, sizeof(struct rte_swx_table_selector_member));
+	if (!m)
+		return -ENOMEM;
+
+	m->member_id = member_id;
+	m->member_weight = member_weight;
+
+	TAILQ_INSERT_TAIL(&group->members, m, node);
+
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id __rte_unused,
+						  uint32_t member_id __rte_unused)
+{
+	struct selector *s;
+	struct rte_swx_table_selector_group *group;
+	struct rte_swx_table_selector_member *m;
+
+	/* Check input arguments. */
+	if (!ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s ||
+	    (group_id >= s->info.n_groups_max) ||
+	    !s->groups_added[group_id] ||
+	    s->groups_pending_delete[group_id])
+		return -EINVAL;
+
+	/* Initialize the pending group, if needed. */
+	if (!s->pending_groups[group_id]) {
+		int status;
+
+		status = selector_group_duplicate_to_pending(s, group_id);
+		if (status)
+			return status;
+	}
+
+	group = s->pending_groups[group_id];
+
+	/* Look for this member in the group and remove it, if found. */
+	TAILQ_FOREACH(m, &group->members, node)
+		if (m->member_id == member_id) {
+			TAILQ_REMOVE(&group->members, m, node);
+			free(m);
+			return 0;
+		}
+
+	return 0;
+}
+
+static int
+selector_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Push pending group member changes (s->pending_groups[group_id]) to the selector table
+	 * mirror copy (ts_next->obj).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+		int status;
+
+		/* Skip this group if no change needed. */
+		if (!group)
+			continue;
+
+		/* Apply the pending changes for the current group. */
+		status = rte_swx_table_selector_group_set(ts_next->obj, group_id, group);
+		if (status)
+			return status;
+	}
+
+	return 0;
+}
+
+static void
+selector_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Commit pending group member changes (s->pending_groups[group_id]) to the stable group
+	 * records (s->groups[group_id).
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *g = s->groups[group_id];
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		/* Skip this group if no change needed. */
+		if (!gp)
+			continue;
+
+		/* Transition the pending changes to stable. */
+		s->groups[group_id] = gp;
+		s->pending_groups[group_id] = NULL;
+
+		/* Free the old group member list. */
+		if (!g)
+			continue;
+
+		for ( ; ; ) {
+			struct rte_swx_table_selector_member *m;
+
+			m = TAILQ_FIRST(&g->members);
+			if (!m)
+				break;
+
+			TAILQ_REMOVE(&g->members, m, node);
+			free(m);
+		}
+
+		free(g);
+	}
+
+	/* Commit pending group validity changes (from s->groups_pending_delete[group_id] to
+	 * s->groups_added[group_id].
+	 */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		if (s->groups_pending_delete[group_id]) {
+			s->groups_added[group_id] = 0;
+			s->groups_pending_delete[group_id] = 0;
+		}
+}
+
+static void
+selector_rollback(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	struct rte_swx_table_state *ts = &ctl->ts[ctl->info.n_tables + selector_id];
+	struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+	uint32_t group_id;
+
+	/* Discard any previous changes to the selector table mirror copy (ts_next->obj). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+		if (gp) {
+			ts_next->obj = ts->obj;
+			break;
+		}
+	}
+}
+
+static void
+selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+	struct selector *s = &ctl->selectors[selector_id];
+	uint32_t group_id;
+
+	/* Discard any pending group member changes (s->pending_groups[group_id]). */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+		selector_pending_group_members_free(s, group_id);
+
+	/* Discard any pending group deletions. */
+	memset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));
+}
+
 int
 rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 {
@@ -1508,8 +2131,8 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 
 	CHECK(ctl, EINVAL);
 
-	/* Operate the changes on the current ts_next before it becomes the new
-	 * ts.
+	/* Operate the changes on the current ts_next before it becomes the new ts. First, operate
+	 * all the changes that can fail; if no failure, then operate the changes that cannot fail.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		status = table_rollfwd0(ctl, i, 0);
@@ -1517,6 +2140,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			goto rollback;
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		status = selector_rollfwd(ctl, i);
+		if (status)
+			goto rollback;
+	}
+
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_rollfwd1(ctl, i);
 
@@ -1529,7 +2158,10 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 	ctl->ts = ctl->ts_next;
 	ctl->ts_next = ts;
 
-	/* Operate the changes on the current ts_next, which is the previous ts.
+	/* Operate the changes on the current ts_next, which is the previous ts, in order to get
+	 * the current ts_next in sync with the current ts. Since the changes that can fail did
+	 * not fail on the previous ts_next, it is guaranteed that they will not fail on the
+	 * current ts_next, hence no error checking is needed.
 	 */
 	for (i = 0; i < ctl->info.n_tables; i++) {
 		table_rollfwd0(ctl, i, 1);
@@ -1537,6 +2169,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 		table_rollfwd2(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollfwd(ctl, i);
+		selector_rollfwd_finalize(ctl, i);
+	}
+
 	return 0;
 
 rollback:
@@ -1546,6 +2183,12 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
 			table_abort(ctl, i);
 	}
 
+	for (i = 0; i < ctl->info.n_selectors; i++) {
+		selector_rollback(ctl, i);
+		if (abort_on_fail)
+			selector_abort(ctl, i);
+	}
+
 	return status;
 }
 
@@ -1559,6 +2202,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)
 
 	for (i = 0; i < ctl->info.n_tables; i++)
 		table_abort(ctl, i);
+
+	for (i = 0; i < ctl->info.n_selectors; i++)
+		selector_abort(ctl, i);
 }
 
 static int
@@ -1858,3 +2504,49 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 		n_entries);
 	return 0;
 }
+
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name)
+{
+	struct selector *s;
+	uint32_t group_id;
+
+	if (!f || !ctl || !selector_name || !selector_name[0])
+		return -EINVAL;
+
+	s = selector_find(ctl, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	/* Selector. */
+	fprintf(f, "# Selector %s: max groups %u, max members per group %u\n",
+		s->info.name,
+		s->info.n_groups_max,
+		s->info.n_members_per_group_max);
+
+	/* Groups. */
+	for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+		struct rte_swx_table_selector_group *group = s->groups[group_id];
+		struct rte_swx_table_selector_member *m;
+		uint32_t n_members = 0;
+
+		fprintf(f, "Group %u = [", group_id);
+
+		/* Non-empty group. */
+		if (group)
+			TAILQ_FOREACH(m, &group->members, node) {
+				fprintf(f, "%u:%u ", m->member_id, m->member_weight);
+				n_members++;
+			}
+
+		/* Empty group. */
+		if (!n_members)
+			fprintf(f, "0:1 ");
+
+		fprintf(f, "]\n");
+	}
+
+	return 0;
+}
diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index dee788be8..f37301cf9 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -22,6 +22,7 @@ extern "C" {
 
 #include "rte_swx_port.h"
 #include "rte_swx_table.h"
+#include "rte_swx_table_selector.h"
 
 struct rte_swx_pipeline;
 
@@ -48,6 +49,9 @@ struct rte_swx_ctl_pipeline_info {
 	/** Number of tables. */
 	uint32_t n_tables;
 
+	/** Number of selector tables. */
+	uint32_t n_selectors;
+
 	/** Number of register arrays. */
 	uint32_t n_regarrays;
 
@@ -385,6 +389,129 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 				      const char *table_name,
 				      struct rte_swx_table_stats *stats);
 
+/*
+ * Selector Table Query API.
+ */
+
+/** Selector info. */
+struct rte_swx_ctl_selector_info {
+	/** Selector table name. */
+	char name[RTE_SWX_CTL_NAME_SIZE];
+
+	/** Number of selector fields. */
+	uint32_t n_selector_fields;
+
+	/** Maximum number of groups. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Selector table info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors* - 1).
+ * @param[out] selector
+ *   Selector table info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector);
+
+/**
+ * Selector table "group ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "group ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+					     uint32_t selector_id,
+					     struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Sselector table selector field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[in] selector_field_id
+ *   Selector table selector field ID (0 .. *n_selector_fields* - 1).
+ * @param[out] field
+ *   Selector table selector field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+				    uint32_t selector_id,
+				    uint32_t selector_field_id,
+				    struct rte_swx_ctl_table_match_field_info *field);
+
+/**
+ * Selector table "member ID" field info get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_id
+ *   Selector table ID (0 .. *n_selectors*).
+ * @param[out] field
+ *   Selector table "member ID" field info.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+					      uint32_t selector_id,
+					      struct rte_swx_ctl_table_match_field_info *field);
+
+/** Selector table statistics. */
+struct rte_swx_pipeline_selector_stats {
+	/** Number of packets. */
+	uint64_t n_pkts;
+};
+
+/**
+ * Selector table statistics counters read
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] stats
+ *   Selector table stats. Must point to a pre-allocated structure.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+					 const char *selector_name,
+					 struct rte_swx_pipeline_selector_stats *stats);
+
 /*
  * Table Update API.
  */
@@ -529,6 +656,111 @@ rte_swx_ctl_pipeline_table_entry_delete(struct rte_swx_ctl_pipeline *ctl,
 					const char *table_name,
 					struct rte_swx_table_entry *entry);
 
+/**
+ * Pipeline selector table group add
+ *
+ * Add a new group to a selector table. This operation is executed before this
+ * function returns and its result is independent of the result of the next
+ * commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[out] group_id
+ *   The ID of the new group. Only valid when the function call is successful.
+ *   This group is initially empty, i.e. it does not contain any members.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOSPC: All groups are currently in use, no group available.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+					const char *selector_name,
+					uint32_t *group_id);
+
+/**
+ * Pipeline selector table group delete
+ *
+ * Schedule a group for deletion as part of the next commit operation. The group
+ * to be deleted can be empty or non-empty.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   Group to be deleted from the selector table.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+					   const char *selector_name,
+					   uint32_t group_id);
+
+/**
+ * Pipeline selector table member add to group
+ *
+ * Schedule the operation to add a new member to an existing group as part of
+ * the next commit operation. If this member is already in this group, the
+ * member weight is updated to the new value. A weight of zero means this member
+ * is to be deleted from the group.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID.
+ * @param[in] member_id
+ *   The member to be added to the group.
+ * @param[in] member_weight
+ *   Member weight.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory;
+ *   -ENOSPC: The group is full.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+					       const char *selector_name,
+					       uint32_t group_id,
+					       uint32_t member_id,
+					       uint32_t member_weight);
+
+/**
+ * Pipeline selector table member delete from group
+ *
+ * Schedule the operation to delete a member from an existing group as part of
+ * the next commit operation.
+ *
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @param[in] group_id
+ *   The group ID. Must be valid.
+ * @param[in] member_id
+ *   The member to be added to the group. Must be valid.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+						  const char *selector_name,
+						  uint32_t group_id,
+						  uint32_t member_id);
+
 /**
  * Pipeline commit
  *
@@ -608,6 +840,27 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 				   struct rte_swx_ctl_pipeline *ctl,
 				   const char *table_name);
 
+/**
+ * Pipeline selector print to file
+ *
+ * Print all the selector entries to file.
+ *
+ * @param[in] f
+ *   Output file.
+ * @param[in] ctl
+ *   Pipeline control handle.
+ * @param[in] selector_name
+ *   Selector table name.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_selector_fprintf(FILE *f,
+				      struct rte_swx_ctl_pipeline *ctl,
+				      const char *selector_name);
+
 /*
  * Register Array Query API.
  */
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index a2732a1e5..1ef8ac7de 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -15,6 +15,8 @@
 #include <rte_cycles.h>
 #include <rte_meter.h>
 
+#include <rte_swx_table_selector.h>
+
 #include "rte_swx_pipeline.h"
 #include "rte_swx_ctl.h"
 
@@ -498,6 +500,7 @@ enum instruction_type {
 
 	/* table TABLE */
 	INSTR_TABLE,
+	INSTR_SELECTOR,
 
 	/* extern e.obj.func */
 	INSTR_EXTERN_OBJ,
@@ -794,6 +797,38 @@ struct table_statistics {
 	uint64_t *n_pkts_action;
 };
 
+/*
+ * Selector.
+ */
+struct selector {
+	TAILQ_ENTRY(selector) node;
+	char name[RTE_SWX_NAME_SIZE];
+
+	struct field *group_id_field;
+	struct field **selector_fields;
+	uint32_t n_selector_fields;
+	struct header *selector_header;
+	struct field *member_id_field;
+
+	uint32_t n_groups_max;
+	uint32_t n_members_per_group_max;
+
+	uint32_t id;
+};
+
+TAILQ_HEAD(selector_tailq, selector);
+
+struct selector_runtime {
+	void *mailbox;
+	uint8_t **group_id_buffer;
+	uint8_t **selector_buffer;
+	uint8_t **member_id_buffer;
+};
+
+struct selector_statistics {
+	uint64_t n_pkts;
+};
+
 /*
  * Register array.
  */
@@ -873,6 +908,7 @@ struct thread {
 
 	/* Tables. */
 	struct table_runtime *tables;
+	struct selector_runtime *selectors;
 	struct rte_swx_table_state *table_state;
 	uint64_t action_id;
 	int hit; /* 0 = Miss, 1 = Hit. */
@@ -1308,6 +1344,7 @@ struct rte_swx_pipeline {
 	struct action_tailq actions;
 	struct table_type_tailq table_types;
 	struct table_tailq tables;
+	struct selector_tailq selectors;
 	struct regarray_tailq regarrays;
 	struct meter_profile_tailq meter_profiles;
 	struct metarray_tailq metarrays;
@@ -1317,6 +1354,7 @@ struct rte_swx_pipeline {
 	struct instruction **action_instructions;
 	struct rte_swx_table_state *table_state;
 	struct table_statistics *table_stats;
+	struct selector_statistics *selector_stats;
 	struct regarray_runtime *regarray_runtime;
 	struct metarray_runtime *metarray_runtime;
 	struct instruction *instructions;
@@ -1329,6 +1367,7 @@ struct rte_swx_pipeline {
 	uint32_t n_extern_funcs;
 	uint32_t n_actions;
 	uint32_t n_tables;
+	uint32_t n_selectors;
 	uint32_t n_regarrays;
 	uint32_t n_metarrays;
 	uint32_t n_headers;
@@ -3450,6 +3489,9 @@ instr_hdr_invalidate_exec(struct rte_swx_pipeline *p)
 static struct table *
 table_find(struct rte_swx_pipeline *p, const char *name);
 
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name);
+
 static int
 instr_table_translate(struct rte_swx_pipeline *p,
 		      struct action *action,
@@ -3459,16 +3501,26 @@ instr_table_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct table *t;
+	struct selector *s;
 
 	CHECK(!action, EINVAL);
 	CHECK(n_tokens == 2, EINVAL);
 
 	t = table_find(p, tokens[1]);
-	CHECK(t, EINVAL);
+	if (t) {
+		instr->type = INSTR_TABLE;
+		instr->table.table_id = t->id;
+		return 0;
+	}
 
-	instr->type = INSTR_TABLE;
-	instr->table.table_id = t->id;
-	return 0;
+	s = selector_find(p, tokens[1]);
+	if (s) {
+		instr->type = INSTR_SELECTOR;
+		instr->table.table_id = s->id;
+		return 0;
+	}
+
+	CHECK(0, EINVAL);
 }
 
 static inline void
@@ -3522,6 +3574,45 @@ instr_table_exec(struct rte_swx_pipeline *p)
 	thread_ip_action_call(p, t, action_id);
 }
 
+static inline void
+instr_selector_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+	uint32_t selector_id = ip->table.table_id;
+	struct rte_swx_table_state *ts = &t->table_state[p->n_tables + selector_id];
+	struct selector_runtime *selector = &t->selectors[selector_id];
+	struct selector_statistics *stats = &p->selector_stats[selector_id];
+	uint64_t n_pkts = stats->n_pkts;
+	int done;
+
+	/* Table. */
+	done = rte_swx_table_selector_select(ts->obj,
+			   selector->mailbox,
+			   selector->group_id_buffer,
+			   selector->selector_buffer,
+			   selector->member_id_buffer);
+	if (!done) {
+		/* Thread. */
+		TRACE("[Thread %2u] selector %u (not finalized)\n",
+		      p->thread_id,
+		      selector_id);
+
+		thread_yield(p);
+		return;
+	}
+
+
+	TRACE("[Thread %2u] selector %u\n",
+	      p->thread_id,
+	      selector_id);
+
+	stats->n_pkts = n_pkts + 1;
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * extern.
  */
@@ -8787,6 +8878,7 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_METER_IMI] = instr_meter_imi_exec,
 
 	[INSTR_TABLE] = instr_table_exec,
+	[INSTR_SELECTOR] = instr_selector_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
 
@@ -9253,6 +9345,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 
 	CHECK_NAME(name, EINVAL);
 	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
 
 	CHECK(params, EINVAL);
 
@@ -9455,82 +9548,6 @@ table_params_free(struct rte_swx_table_params *params)
 	free(params);
 }
 
-static int
-table_state_build(struct rte_swx_pipeline *p)
-{
-	struct table *table;
-
-	p->table_state = calloc(p->n_tables,
-				sizeof(struct rte_swx_table_state));
-	CHECK(p->table_state, ENOMEM);
-
-	TAILQ_FOREACH(table, &p->tables, node) {
-		struct rte_swx_table_state *ts = &p->table_state[table->id];
-
-		if (table->type) {
-			struct rte_swx_table_params *params;
-
-			/* ts->obj. */
-			params = table_params_get(table);
-			CHECK(params, ENOMEM);
-
-			ts->obj = table->type->ops.create(params,
-				NULL,
-				table->args,
-				p->numa_node);
-
-			table_params_free(params);
-			CHECK(ts->obj, ENODEV);
-		}
-
-		/* ts->default_action_data. */
-		if (table->action_data_size_max) {
-			ts->default_action_data =
-				malloc(table->action_data_size_max);
-			CHECK(ts->default_action_data, ENOMEM);
-
-			memcpy(ts->default_action_data,
-			       table->default_action_data,
-			       table->action_data_size_max);
-		}
-
-		/* ts->default_action_id. */
-		ts->default_action_id = table->default_action->id;
-	}
-
-	return 0;
-}
-
-static void
-table_state_build_free(struct rte_swx_pipeline *p)
-{
-	uint32_t i;
-
-	if (!p->table_state)
-		return;
-
-	for (i = 0; i < p->n_tables; i++) {
-		struct rte_swx_table_state *ts = &p->table_state[i];
-		struct table *table = table_find_by_id(p, i);
-
-		/* ts->obj. */
-		if (table->type && ts->obj)
-			table->type->ops.free(ts->obj);
-
-		/* ts->default_action_data. */
-		free(ts->default_action_data);
-	}
-
-	free(p->table_state);
-	p->table_state = NULL;
-}
-
-static void
-table_state_free(struct rte_swx_pipeline *p)
-{
-	table_state_build_free(p);
-}
-
 static int
 table_stub_lkp(void *table __rte_unused,
 	       void *mailbox __rte_unused,
@@ -9658,6 +9675,458 @@ table_free(struct rte_swx_pipeline *p)
 	}
 }
 
+/*
+ * Selector.
+ */
+static struct selector *
+selector_find(struct rte_swx_pipeline *p, const char *name)
+{
+	struct selector *s;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (strcmp(s->name, name) == 0)
+			return s;
+
+	return NULL;
+}
+
+static struct selector *
+selector_find_by_id(struct rte_swx_pipeline *p, uint32_t id)
+{
+	struct selector *s = NULL;
+
+	TAILQ_FOREACH(s, &p->selectors, node)
+		if (s->id == id)
+			return s;
+
+	return NULL;
+}
+
+static int
+selector_fields_check(struct rte_swx_pipeline *p,
+		      struct rte_swx_pipeline_selector_params *params,
+		      struct header **header)
+{
+	struct header *h0 = NULL;
+	struct field *hf, *mf;
+	uint32_t i;
+
+	/* Return if no selector fields. */
+	if (!params->n_selector_fields || !params->selector_field_names)
+		return -EINVAL;
+
+	/* Check that all the selector fields either belong to the same header
+	 * or are all meta-data fields.
+	 */
+	hf = header_field_parse(p, params->selector_field_names[0], &h0);
+	mf = metadata_field_parse(p, params->selector_field_names[0]);
+	if (!hf && !mf)
+		return -EINVAL;
+
+	for (i = 1; i < params->n_selector_fields; i++)
+		if (h0) {
+			struct header *h;
+
+			hf = header_field_parse(p, params->selector_field_names[i], &h);
+			if (!hf || (h->id != h0->id))
+				return -EINVAL;
+		} else {
+			mf = metadata_field_parse(p, params->selector_field_names[i]);
+			if (!mf)
+				return -EINVAL;
+		}
+
+	/* Check that there are no duplicated match fields. */
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+		uint32_t j;
+
+		for (j = i + 1; j < params->n_selector_fields; j++)
+			if (!strcmp(params->selector_field_names[j], field_name))
+				return -EINVAL;
+	}
+
+	/* Return. */
+	if (header)
+		*header = h0;
+
+	return 0;
+}
+
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params)
+{
+	struct selector *s;
+	struct header *selector_header = NULL;
+	struct field *group_id_field, *member_id_field;
+	uint32_t i;
+	int status = 0;
+
+	CHECK(p, EINVAL);
+
+	CHECK_NAME(name, EINVAL);
+	CHECK(!table_find(p, name), EEXIST);
+	CHECK(!selector_find(p, name), EEXIST);
+
+	CHECK(params, EINVAL);
+
+	CHECK_NAME(params->group_id_field_name, EINVAL);
+	group_id_field = metadata_field_parse(p, params->group_id_field_name);
+	CHECK(group_id_field, EINVAL);
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		CHECK_NAME(field_name, EINVAL);
+	}
+	status = selector_fields_check(p, params, &selector_header);
+	if (status)
+		return status;
+
+	CHECK_NAME(params->member_id_field_name, EINVAL);
+	member_id_field = metadata_field_parse(p, params->member_id_field_name);
+	CHECK(member_id_field, EINVAL);
+
+	CHECK(params->n_groups_max, EINVAL);
+
+	CHECK(params->n_members_per_group_max, EINVAL);
+
+	/* Memory allocation. */
+	s = calloc(1, sizeof(struct selector));
+	if (!s) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->selector_fields = calloc(params->n_selector_fields, sizeof(struct field *));
+	if (!s->selector_fields) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Node initialization. */
+	strcpy(s->name, name);
+
+	s->group_id_field = group_id_field;
+
+	for (i = 0; i < params->n_selector_fields; i++) {
+		const char *field_name = params->selector_field_names[i];
+
+		s->selector_fields[i] = selector_header ?
+			header_field_parse(p, field_name, NULL) :
+			metadata_field_parse(p, field_name);
+	}
+
+	s->n_selector_fields = params->n_selector_fields;
+
+	s->selector_header = selector_header;
+
+	s->member_id_field = member_id_field;
+
+	s->n_groups_max = params->n_groups_max;
+
+	s->n_members_per_group_max = params->n_members_per_group_max;
+
+	s->id = p->n_selectors;
+
+	/* Node add to tailq. */
+	TAILQ_INSERT_TAIL(&p->selectors, s, node);
+	p->n_selectors++;
+
+	return 0;
+
+error:
+	if (!s)
+		return status;
+
+	free(s->selector_fields);
+
+	free(s);
+
+	return status;
+}
+
+static void
+selector_params_free(struct rte_swx_table_selector_params *params)
+{
+	if (!params)
+		return;
+
+	free(params->selector_mask);
+
+	free(params);
+}
+
+static struct rte_swx_table_selector_params *
+selector_table_params_get(struct selector *s)
+{
+	struct rte_swx_table_selector_params *params = NULL;
+	struct field *first, *last;
+	uint32_t i;
+
+	/* Memory allocation. */
+	params = calloc(1, sizeof(struct rte_swx_table_selector_params));
+	if (!params)
+		goto error;
+
+	/* Group ID. */
+	params->group_id_offset = s->group_id_field->offset / 8;
+
+	/* Find first (smallest offset) and last (biggest offset) selector fields. */
+	first = s->selector_fields[0];
+	last = s->selector_fields[0];
+
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+
+		if (f->offset < first->offset)
+			first = f;
+
+		if (f->offset > last->offset)
+			last = f;
+	}
+
+	/* Selector offset and size. */
+	params->selector_offset = first->offset / 8;
+	params->selector_size = (last->offset + last->n_bits - first->offset) / 8;
+
+	/* Memory allocation. */
+	params->selector_mask = calloc(1, params->selector_size);
+	if (!params->selector_mask)
+		goto error;
+
+	/* Selector mask. */
+	for (i = 0; i < s->n_selector_fields; i++) {
+		struct field *f = s->selector_fields[i];
+		uint32_t start = (f->offset - first->offset) / 8;
+		size_t size = f->n_bits / 8;
+
+		memset(&params->selector_mask[start], 0xFF, size);
+	}
+
+	/* Member ID. */
+	params->member_id_offset = s->member_id_field->offset / 8;
+
+	/* Maximum number of groups. */
+	params->n_groups_max = s->n_groups_max;
+
+	/* Maximum number of members per group. */
+	params->n_members_per_group_max = s->n_members_per_group_max;
+
+	return params;
+
+error:
+	selector_params_free(params);
+	return NULL;
+}
+
+static void
+selector_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		uint32_t j;
+
+		if (!t->selectors)
+			continue;
+
+		for (j = 0; j < p->n_selectors; j++) {
+			struct selector_runtime *r = &t->selectors[j];
+
+			free(r->mailbox);
+		}
+
+		free(t->selectors);
+		t->selectors = NULL;
+	}
+
+	free(p->selector_stats);
+	p->selector_stats = NULL;
+}
+
+static int
+selector_build(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+	int status = 0;
+
+	/* Per pipeline: selector statistics. */
+	p->selector_stats = calloc(p->n_selectors, sizeof(struct selector_statistics));
+	if (!p->selector_stats) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	/* Per thread: selector run-time. */
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+		struct selector *s;
+
+		t->selectors = calloc(p->n_selectors, sizeof(struct selector_runtime));
+		if (!t->selectors) {
+			status = -ENOMEM;
+			goto error;
+		}
+
+		TAILQ_FOREACH(s, &p->selectors, node) {
+			struct selector_runtime *r = &t->selectors[s->id];
+			uint64_t size;
+
+			/* r->mailbox. */
+			size = rte_swx_table_selector_mailbox_size_get();
+			if (size) {
+				r->mailbox = calloc(1, size);
+				if (!r->mailbox) {
+					status = -ENOMEM;
+					goto error;
+				}
+			}
+
+			/* r->group_id_buffer. */
+			r->group_id_buffer = &t->structs[p->metadata_struct_id];
+
+			/* r->selector_buffer. */
+			r->selector_buffer = s->selector_header ?
+				&t->structs[s->selector_header->struct_id] :
+				&t->structs[p->metadata_struct_id];
+
+			/* r->member_id_buffer. */
+			r->member_id_buffer = &t->structs[p->metadata_struct_id];
+		}
+	}
+
+	return 0;
+
+error:
+	selector_build_free(p);
+	return status;
+}
+
+static void
+selector_free(struct rte_swx_pipeline *p)
+{
+	selector_build_free(p);
+
+	/* Selector tables. */
+	for ( ; ; ) {
+		struct selector *elem;
+
+		elem = TAILQ_FIRST(&p->selectors);
+		if (!elem)
+			break;
+
+		TAILQ_REMOVE(&p->selectors, elem, node);
+		free(elem->selector_fields);
+		free(elem);
+	}
+}
+
+/*
+ * Table state.
+ */
+static int
+table_state_build(struct rte_swx_pipeline *p)
+{
+	struct table *table;
+	struct selector *s;
+
+	p->table_state = calloc(p->n_tables + p->n_selectors,
+				sizeof(struct rte_swx_table_state));
+	CHECK(p->table_state, ENOMEM);
+
+	TAILQ_FOREACH(table, &p->tables, node) {
+		struct rte_swx_table_state *ts = &p->table_state[table->id];
+
+		if (table->type) {
+			struct rte_swx_table_params *params;
+
+			/* ts->obj. */
+			params = table_params_get(table);
+			CHECK(params, ENOMEM);
+
+			ts->obj = table->type->ops.create(params,
+				NULL,
+				table->args,
+				p->numa_node);
+
+			table_params_free(params);
+			CHECK(ts->obj, ENODEV);
+		}
+
+		/* ts->default_action_data. */
+		if (table->action_data_size_max) {
+			ts->default_action_data =
+				malloc(table->action_data_size_max);
+			CHECK(ts->default_action_data, ENOMEM);
+
+			memcpy(ts->default_action_data,
+			       table->default_action_data,
+			       table->action_data_size_max);
+		}
+
+		/* ts->default_action_id. */
+		ts->default_action_id = table->default_action->id;
+	}
+
+	TAILQ_FOREACH(s, &p->selectors, node) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
+		struct rte_swx_table_selector_params *params;
+
+		/* ts->obj. */
+		params = selector_table_params_get(s);
+		CHECK(params, ENOMEM);
+
+		ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+
+		selector_params_free(params);
+		CHECK(ts->obj, ENODEV);
+	}
+
+	return 0;
+}
+
+static void
+table_state_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	if (!p->table_state)
+		return;
+
+	for (i = 0; i < p->n_tables; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[i];
+		struct table *table = table_find_by_id(p, i);
+
+		/* ts->obj. */
+		if (table->type && ts->obj)
+			table->type->ops.free(ts->obj);
+
+		/* ts->default_action_data. */
+		free(ts->default_action_data);
+	}
+
+	for (i = 0; i < p->n_selectors; i++) {
+		struct rte_swx_table_state *ts = &p->table_state[p->n_tables + i];
+
+		/* ts->obj. */
+		if (ts->obj)
+			rte_swx_table_selector_free(ts->obj);
+	}
+
+	free(p->table_state);
+	p->table_state = NULL;
+}
+
+static void
+table_state_free(struct rte_swx_pipeline *p)
+{
+	table_state_build_free(p);
+}
+
 /*
  * Register array.
  */
@@ -9988,6 +10457,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node)
 	TAILQ_INIT(&pipeline->actions);
 	TAILQ_INIT(&pipeline->table_types);
 	TAILQ_INIT(&pipeline->tables);
+	TAILQ_INIT(&pipeline->selectors);
 	TAILQ_INIT(&pipeline->regarrays);
 	TAILQ_INIT(&pipeline->meter_profiles);
 	TAILQ_INIT(&pipeline->metarrays);
@@ -10010,6 +10480,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)
 	metarray_free(p);
 	regarray_free(p);
 	table_state_free(p);
+	selector_free(p);
 	table_free(p);
 	action_free(p);
 	metadata_free(p);
@@ -10089,6 +10560,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	if (status)
 		goto error;
 
+	status = selector_build(p);
+	if (status)
+		goto error;
+
 	status = table_state_build(p);
 	if (status)
 		goto error;
@@ -10108,6 +10583,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	metarray_build_free(p);
 	regarray_build_free(p);
 	table_state_build_free(p);
+	selector_build_free(p);
 	table_build_free(p);
 	action_build_free(p);
 	metadata_build_free(p);
@@ -10167,6 +10643,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,
 	pipeline->n_ports_out = p->n_ports_out;
 	pipeline->n_actions = n_actions;
 	pipeline->n_tables = n_tables;
+	pipeline->n_selectors = p->n_selectors;
 	pipeline->n_regarrays = p->n_regarrays;
 	pipeline->n_metarrays = p->n_metarrays;
 
@@ -10320,6 +10797,98 @@ rte_swx_ctl_table_ops_get(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_selector_info_get(struct rte_swx_pipeline *p,
+			      uint32_t selector_id,
+			      struct rte_swx_ctl_selector_info *selector)
+{
+	struct selector *s = NULL;
+
+	if (!p || !selector)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	strcpy(selector->name, s->name);
+
+	selector->n_selector_fields = s->n_selector_fields;
+	selector->n_groups_max = s->n_groups_max;
+	selector->n_members_per_group_max = s->n_members_per_group_max;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_group_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->group_id_field->n_bits;
+	field->offset = s->group_id_field->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 uint32_t selector_field_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+	struct field *f;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s || (selector_field_id >= s->n_selector_fields))
+		return -EINVAL;
+
+	f = s->selector_fields[selector_field_id];
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = s->selector_header ? 1 : 0;
+	field->n_bits = f->n_bits;
+	field->offset = f->offset;
+
+	return 0;
+}
+
+int
+rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p,
+	 uint32_t selector_id,
+	 struct rte_swx_ctl_table_match_field_info *field)
+{
+	struct selector *s;
+
+	if (!p || (selector_id >= p->n_selectors) || !field)
+		return -EINVAL;
+
+	s = selector_find_by_id(p, selector_id);
+	if (!s)
+		return -EINVAL;
+
+	field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+	field->is_header = 0;
+	field->n_bits = s->member_id_field->n_bits;
+	field->offset = s->member_id_field->offset;
+
+	return 0;
+}
+
 int
 rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
 				 struct rte_swx_table_state **table_state)
@@ -10405,6 +10974,25 @@ rte_swx_ctl_pipeline_table_stats_read(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p,
+	const char *selector_name,
+	struct rte_swx_pipeline_selector_stats *stats)
+{
+	struct selector *s;
+
+	if (!p || !selector_name || !selector_name[0] || !stats)
+		return -EINVAL;
+
+	s = selector_find(p, selector_name);
+	if (!s)
+		return -EINVAL;
+
+	stats->n_pkts = p->selector_stats[s->id].n_pkts;
+
+	return 0;
+}
+
 int
 rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,
 			      uint32_t regarray_id,
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index feeb10a5c..cd395ac39 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -612,6 +612,57 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p,
 			      const char *args,
 			      uint32_t size);
 
+/** Pipeline selector table parameters. */
+struct rte_swx_pipeline_selector_params {
+	/** The group ID field. Input into the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *group_id_field_name;
+
+	/** The set of fields used to select (through a hashing scheme) the
+	 * member within the current group. Inputs into the seletion operation.
+	 * Restriction: All the selector fields must be part of the same struct,
+	 * i.e. part of the same header or part of the meta-data structure.
+	 */
+	const char **selector_field_names;
+
+	/** The number of selector fields. Must be non-zero. */
+	uint32_t n_selector_fields;
+
+	/** The member ID field. Output from the selection operation.
+	 * Restriction: This field must be a meta-data field.
+	 */
+	const char *member_id_field_name;
+
+	/** Maximum number of groups. Must be non-zero. */
+	uint32_t n_groups_max;
+
+	/** Maximum number of members per group. Must be non-zero. */
+	uint32_t n_members_per_group_max;
+};
+
+/**
+ * Pipeline selector table configure
+ *
+ * @param[out] p
+ *   Pipeline handle.
+ * @param[in] name
+ *   Selector table name.
+ * @param[in] params
+ *   Selector table parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough space/cannot allocate memory;
+ *   -EEXIST: Selector table with this name already exists;
+ *   -ENODEV: Selector table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p,
+				 const char *name,
+				 struct rte_swx_pipeline_selector_params *params);
+
 /**
  * Pipeline register array configure
  *
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index 2e867d7bf..6980b0390 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -18,7 +18,9 @@
 #define TABLE_BLOCK 2
 #define TABLE_KEY_BLOCK 3
 #define TABLE_ACTIONS_BLOCK 4
-#define APPLY_BLOCK 5
+#define SELECTOR_BLOCK 5
+#define SELECTOR_SELECTOR_BLOCK 6
+#define APPLY_BLOCK 7
 
 /*
  * extobj.
@@ -940,6 +942,307 @@ table_block_parse(struct table_spec *s,
 	return -EINVAL;
 }
 
+/*
+ * selector.
+ *
+ * selector SELECTOR_NAME {
+ *	group_id FIELD_NAME
+ *	selector {
+ *		FIELD_NAME
+ *		...
+ *	}
+ *	member_id FIELD_NAME
+ *	n_groups N_GROUPS
+ *	n_members_per_group N_MEMBERS_PER_GROUP
+ * }
+ */
+struct selector_spec {
+	char *name;
+	struct rte_swx_pipeline_selector_params params;
+};
+
+static void
+selector_spec_free(struct selector_spec *s)
+{
+	uintptr_t field_name;
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	/* name. */
+	free(s->name);
+	s->name = NULL;
+
+	/* params->group_id_field_name. */
+	field_name = (uintptr_t)s->params.group_id_field_name;
+	free((void *)field_name);
+	s->params.group_id_field_name = NULL;
+
+	/* params->selector_field_names. */
+	for (i = 0; i < s->params.n_selector_fields; i++) {
+		field_name = (uintptr_t)s->params.selector_field_names[i];
+
+		free((void *)field_name);
+	}
+
+	free(s->params.selector_field_names);
+	s->params.selector_field_names = NULL;
+
+	s->params.n_selector_fields = 0;
+
+	/* params->member_id_field_name. */
+	field_name = (uintptr_t)s->params.member_id_field_name;
+	free((void *)field_name);
+	s->params.member_id_field_name = NULL;
+
+	/* params->n_groups_max. */
+	s->params.n_groups_max = 0;
+
+	/* params->n_members_per_group_max. */
+	s->params.n_members_per_group_max = 0;
+}
+
+static int
+selector_statement_parse(struct selector_spec *s,
+			 uint32_t *block_mask,
+			 char **tokens,
+			 uint32_t n_tokens,
+			 uint32_t n_lines,
+			 uint32_t *err_line,
+			 const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 3) || strcmp(tokens[2], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	if (!s->name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_statement_parse(uint32_t *block_mask,
+				  char **tokens,
+				  uint32_t n_tokens,
+				  uint32_t n_lines,
+				  uint32_t *err_line,
+				  const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << SELECTOR_SELECTOR_BLOCK;
+
+	return 0;
+}
+
+static int
+selector_selector_block_parse(struct selector_spec *s,
+			      uint32_t *block_mask,
+			      char **tokens,
+			      uint32_t n_tokens,
+			      uint32_t n_lines,
+			      uint32_t *err_line,
+			      const char **err_msg)
+{
+	const char **new_fields;
+	char *name;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_SELECTOR_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid selector field statement.";
+		return -EINVAL;
+	}
+
+	name = strdup(tokens[0]);
+	if (!name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_fields = realloc(s->params.selector_field_names,
+			     (s->params.n_selector_fields + 1) * sizeof(char *));
+	if (!new_fields) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.selector_field_names = new_fields;
+	s->params.selector_field_names[s->params.n_selector_fields] = name;
+	s->params.n_selector_fields++;
+
+	return 0;
+}
+
+static int
+selector_block_parse(struct selector_spec *s,
+		     uint32_t *block_mask,
+		     char **tokens,
+		     uint32_t n_tokens,
+		     uint32_t n_lines,
+		     uint32_t *err_line,
+		     const char **err_msg)
+{
+	if (*block_mask & (1 << SELECTOR_SELECTOR_BLOCK))
+		return selector_selector_block_parse(s,
+						     block_mask,
+						     tokens,
+						     n_tokens,
+						     n_lines,
+						     err_line,
+						     err_msg);
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << SELECTOR_BLOCK);
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "group_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid group_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.group_id_field_name = strdup(tokens[1]);
+		if (!s->params.group_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "selector"))
+		return selector_selector_statement_parse(block_mask,
+							 tokens,
+							 n_tokens,
+							 n_lines,
+							 err_line,
+							 err_msg);
+
+	if (!strcmp(tokens[0], "member_id")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid member_id statement.";
+			return -EINVAL;
+		}
+
+		s->params.member_id_field_name = strdup(tokens[1]);
+		if (!s->params.member_id_field_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_groups_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_groups_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_groups argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "n_members_per_group_max")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group statement.";
+			return -EINVAL;
+		}
+
+		s->params.n_members_per_group_max = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid n_members_per_group argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* Anything else. */
+	if (err_line)
+		*err_line = n_lines;
+	if (err_msg)
+		*err_msg = "Invalid statement.";
+	return -EINVAL;
+}
+
 /*
  * regarray.
  *
@@ -1203,6 +1506,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	struct metadata_spec metadata_spec = {0};
 	struct action_spec action_spec = {0};
 	struct table_spec table_spec = {0};
+	struct selector_spec selector_spec = {0};
 	struct regarray_spec regarray_spec = {0};
 	struct metarray_spec metarray_spec = {0};
 	struct apply_spec apply_spec = {0};
@@ -1386,6 +1690,38 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector block. */
+		if (block_mask & (1 << SELECTOR_BLOCK)) {
+			status = selector_block_parse(&selector_spec,
+						      &block_mask,
+						      tokens,
+						      n_tokens,
+						      n_lines,
+						      err_line,
+						      err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << SELECTOR_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_selector_config(p,
+				selector_spec.name,
+				&selector_spec.params);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Selector configuration error.";
+				goto error;
+			}
+
+			selector_spec_free(&selector_spec);
+
+			continue;
+		}
+
 		/* apply block. */
 		if (block_mask & (1 << APPLY_BLOCK)) {
 			status = apply_block_parse(&apply_spec,
@@ -1544,6 +1880,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 			continue;
 		}
 
+		/* selector. */
+		if (!strcmp(tokens[0], "selector")) {
+			status = selector_statement_parse(&selector_spec,
+							  &block_mask,
+							  tokens,
+							  n_tokens,
+							  n_lines,
+							  err_line,
+							  err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
 		/* regarray. */
 		if (!strcmp(tokens[0], "regarray")) {
 			status = regarray_statement_parse(&regarray_spec,
@@ -1651,6 +2002,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 	metadata_spec_free(&metadata_spec);
 	action_spec_free(&action_spec);
 	table_spec_free(&table_spec);
+	selector_spec_free(&selector_spec);
 	regarray_spec_free(&regarray_spec);
 	metarray_spec_free(&metarray_spec);
 	apply_spec_free(&apply_spec);
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index a4d7d9788..ff0974c2e 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -116,4 +116,17 @@ EXPERIMENTAL {
 	rte_swx_ctl_regarray_info_get;
 	rte_swx_pipeline_metarray_config;
 	rte_swx_pipeline_regarray_config;
+
+	#added in 21.08
+	rte_swx_pipeline_selector_config;
+	rte_swx_ctl_pipeline_selector_fprintf;
+	rte_swx_ctl_pipeline_selector_group_add;
+	rte_swx_ctl_pipeline_selector_group_delete;
+	rte_swx_ctl_pipeline_selector_group_member_add;
+	rte_swx_ctl_pipeline_selector_group_member_delete;
+	rte_swx_ctl_pipeline_selector_stats_read;
+	rte_swx_ctl_selector_info_get;
+	rte_swx_ctl_selector_field_info_get;
+	rte_swx_ctl_selector_group_id_field_info_get;
+	rte_swx_ctl_selector_member_id_field_info_get;
 };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for selector tables
  2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 2/5] table: add support for selector tables Cristian Dumitrescu
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 3/5] pipeline: " Cristian Dumitrescu
@ 2021-07-10  0:20         ` Cristian Dumitrescu
  2021-07-11 11:27           ` Ali Alnubani
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 5/5] examples/pipeline: add selector example Cristian Dumitrescu
  3 siblings, 1 reply; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-10  0:20 UTC (permalink / raw)
  To: dev, thomas; +Cc: Churchill Khangar

Add application-evel support for selector tables.

Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c | 563 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 563 insertions(+)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index 30754e319..f67783c8f 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -1368,6 +1368,467 @@ cmd_pipeline_table_show(char **tokens,
 		snprintf(out, out_size, MSG_ARG_INVALID, "table_name");
 }
 
+static const char cmd_pipeline_selector_group_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group add\n";
+
+static void
+cmd_pipeline_selector_group_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 6) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group add");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_add(p->ctl,
+		selector_name,
+		&group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+	else
+		snprintf(out, out_size, "Group ID: %u\n", group_id);
+}
+
+static const char cmd_pipeline_selector_group_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group delete <group_id>\n";
+
+static void
+cmd_pipeline_selector_group_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	uint32_t group_id;
+	int status;
+
+	if (n_tokens != 7) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group delete");
+		return;
+	}
+
+	if (parser_read_uint32(&group_id, tokens[6]) != 0) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "group_id");
+		return;
+	}
+
+	status = rte_swx_ctl_pipeline_selector_group_delete(p->ctl,
+		selector_name,
+		group_id);
+	if (status)
+		snprintf(out, out_size, MSG_CMD_FAIL, tokens[0]);
+}
+
+#define GROUP_MEMBER_INFO_TOKENS_MAX 6
+
+static int
+token_is_comment(const char *token)
+{
+	if ((token[0] == '#') ||
+	    (token[0] == ';') ||
+	    ((token[0] == '/') && (token[1] == '/')))
+		return 1; /* TRUE. */
+
+	return 0; /* FALSE. */
+}
+
+static int
+pipeline_selector_group_member_read(const char *string,
+				      uint32_t *group_id,
+				      uint32_t *member_id,
+				      uint32_t *weight,
+				      int *is_blank_or_comment)
+{
+	char *token_array[GROUP_MEMBER_INFO_TOKENS_MAX], **tokens;
+	char *s0 = NULL, *s;
+	uint32_t n_tokens = 0, group_id_val, member_id_val, weight_val;
+	int blank_or_comment = 0;
+
+	/* Check input arguments. */
+	if (!string || !string[0])
+		goto error;
+
+	/* Memory allocation. */
+	s0 = strdup(string);
+	if (!s0)
+		goto error;
+
+	/* Parse the string into tokens. */
+	for (s = s0; ; ) {
+		char *token;
+
+		token = strtok_r(s, " \f\n\r\t\v", &s);
+		if (!token || token_is_comment(token))
+			break;
+
+		if (n_tokens > GROUP_MEMBER_INFO_TOKENS_MAX)
+			goto error;
+
+		token_array[n_tokens] = token;
+		n_tokens++;
+	}
+
+	if (!n_tokens) {
+		blank_or_comment = 1;
+		goto error;
+	}
+
+	tokens = token_array;
+
+	if (n_tokens < 4 ||
+		strcmp(tokens[0], "group") ||
+		strcmp(tokens[2], "member"))
+		goto error;
+
+	/*
+	 * Group ID.
+	 */
+	if (parser_read_uint32(&group_id_val, tokens[1]) != 0)
+		goto error;
+	*group_id = group_id_val;
+
+	/*
+	 * Member ID.
+	 */
+	if (parser_read_uint32(&member_id_val, tokens[3]) != 0)
+		goto error;
+	*member_id = member_id_val;
+
+	tokens += 4;
+	n_tokens -= 4;
+
+	/*
+	 * Weight.
+	 */
+	if (n_tokens && !strcmp(tokens[0], "weight")) {
+		if (n_tokens < 2)
+			goto error;
+
+		if (parser_read_uint32(&weight_val, tokens[1]) != 0)
+			goto error;
+		*weight = weight_val;
+
+		tokens += 2;
+		n_tokens -= 2;
+	}
+
+	if (n_tokens)
+		goto error;
+
+	free(s0);
+	return 0;
+
+error:
+	free(s0);
+	if (is_blank_or_comment)
+		*is_blank_or_comment = blank_or_comment;
+	return -EINVAL;
+}
+
+static int
+pipeline_selector_group_members_add(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_add(p,
+			selector_name,
+			group_id,
+			member_id,
+			weight);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_add_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member add <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_add(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "add")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member add");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_add(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static int
+pipeline_selector_group_members_delete(struct rte_swx_ctl_pipeline *p,
+			   const char *selector_name,
+			   FILE *file,
+			   uint32_t *file_line_number)
+{
+	char *line = NULL;
+	uint32_t line_id = 0;
+	int status = 0;
+
+	/* Buffer allocation. */
+	line = malloc(MAX_LINE_SIZE);
+	if (!line)
+		return -ENOMEM;
+
+	/* File read. */
+	for (line_id = 1; ; line_id++) {
+		uint32_t group_id, member_id, weight;
+		int is_blank_or_comment;
+
+		if (fgets(line, MAX_LINE_SIZE, file) == NULL)
+			break;
+
+		status = pipeline_selector_group_member_read(line,
+							      &group_id,
+							      &member_id,
+							      &weight,
+							      &is_blank_or_comment);
+		if (status) {
+			if (is_blank_or_comment)
+				continue;
+
+			goto error;
+		}
+
+		status = rte_swx_ctl_pipeline_selector_group_member_delete(p,
+			selector_name,
+			group_id,
+			member_id);
+		if (status)
+			goto error;
+	}
+
+error:
+	free(line);
+	*file_line_number = line_id;
+	return status;
+}
+
+static const char cmd_pipeline_selector_group_member_delete_help[] =
+"pipeline <pipeline_name> selector <selector_name> group member delete <file_name>";
+
+static void
+cmd_pipeline_selector_group_member_delete(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name, *file_name;
+	FILE *file = NULL;
+	uint32_t file_line_number = 0;
+	int status;
+
+	if (n_tokens != 8) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	if (strcmp(tokens[2], "selector") != 0) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "selector");
+		return;
+	}
+
+	selector_name = tokens[3];
+
+	if (strcmp(tokens[4], "group") ||
+		strcmp(tokens[5], "member") ||
+		strcmp(tokens[6], "delete")) {
+		snprintf(out, out_size, MSG_ARG_NOT_FOUND, "group member delete");
+		return;
+	}
+
+	file_name = tokens[7];
+	file = fopen(file_name, "r");
+	if (!file) {
+		snprintf(out, out_size, "Cannot open file %s.\n", file_name);
+		return;
+	}
+
+	status = pipeline_selector_group_members_delete(p->ctl,
+					    selector_name,
+					    file,
+					    &file_line_number);
+	if (status)
+		snprintf(out, out_size, "Invalid entry in file %s at line %u\n",
+			 file_name,
+			 file_line_number);
+
+	fclose(file);
+}
+
+static const char cmd_pipeline_selector_show_help[] =
+"pipeline <pipeline_name> selector <selector_name> show\n";
+
+static void
+cmd_pipeline_selector_show(char **tokens,
+	uint32_t n_tokens,
+	char *out,
+	size_t out_size,
+	void *obj)
+{
+	struct pipeline *p;
+	char *pipeline_name, *selector_name;
+	int status;
+
+	if (n_tokens != 5) {
+		snprintf(out, out_size, MSG_ARG_MISMATCH, tokens[0]);
+		return;
+	}
+
+	pipeline_name = tokens[1];
+	p = pipeline_find(obj, pipeline_name);
+	if (!p || !p->ctl) {
+		snprintf(out, out_size, MSG_ARG_INVALID, "pipeline_name");
+		return;
+	}
+
+	selector_name = tokens[3];
+	status = rte_swx_ctl_pipeline_selector_fprintf(stdout,
+		p->ctl, selector_name);
+	if (status)
+		snprintf(out, out_size, MSG_ARG_INVALID, "selector_name");
+}
+
 static const char cmd_pipeline_commit_help[] =
 "pipeline <pipeline_name> commit\n";
 
@@ -2168,6 +2629,11 @@ cmd_help(char **tokens,
 			"\tpipeline table delete\n"
 			"\tpipeline table default\n"
 			"\tpipeline table show\n"
+			"\tpipeline selector group add\n"
+			"\tpipeline selector group delete\n"
+			"\tpipeline selector group member add\n"
+			"\tpipeline selector group member delete\n"
+			"\tpipeline selector show\n"
 			"\tpipeline commit\n"
 			"\tpipeline abort\n"
 			"\tpipeline regrd\n"
@@ -2266,6 +2732,57 @@ cmd_help(char **tokens,
 		return;
 	}
 
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 4) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "add") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_add_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 5) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "group") == 0) &&
+		(strcmp(tokens[3], "member") == 0) &&
+		(strcmp(tokens[4], "delete") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_group_member_delete_help);
+		return;
+	}
+
+	if ((strcmp(tokens[0], "pipeline") == 0) &&
+		(n_tokens == 3) &&
+		(strcmp(tokens[1], "selector") == 0) &&
+		(strcmp(tokens[2], "show") == 0)) {
+		snprintf(out, out_size, "\n%s\n",
+			cmd_pipeline_selector_show_help);
+		return;
+	}
+
 	if ((strcmp(tokens[0], "pipeline") == 0) &&
 		(n_tokens == 2) &&
 		(strcmp(tokens[1], "commit") == 0)) {
@@ -2468,6 +2985,52 @@ cli_process(char *in, char *out, size_t out_size, void *obj)
 			return;
 		}
 
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "add") == 0)) {
+			cmd_pipeline_selector_group_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 6) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "delete") == 0)) {
+			cmd_pipeline_selector_group_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "add") == 0)) {
+			cmd_pipeline_selector_group_member_add(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 7) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "group") == 0) &&
+			(strcmp(tokens[5], "member") == 0) &&
+			(strcmp(tokens[6], "delete") == 0)) {
+			cmd_pipeline_selector_group_member_delete(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
+		if ((n_tokens >= 5) &&
+			(strcmp(tokens[2], "selector") == 0) &&
+			(strcmp(tokens[4], "show") == 0)) {
+			cmd_pipeline_selector_show(tokens, n_tokens, out,
+				out_size, obj);
+			return;
+		}
+
 		if ((n_tokens >= 3) &&
 			(strcmp(tokens[2], "commit") == 0)) {
 			cmd_pipeline_commit(tokens, n_tokens, out,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [dpdk-dev] [PATCH V4 5/5] examples/pipeline: add selector example
  2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
                           ` (2 preceding siblings ...)
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 4/5] examples/pipeline: " Cristian Dumitrescu
@ 2021-07-10  0:20         ` Cristian Dumitrescu
  3 siblings, 0 replies; 24+ messages in thread
From: Cristian Dumitrescu @ 2021-07-10  0:20 UTC (permalink / raw)
  To: dev, thomas

Added the files to illustrate the selector table usage.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/examples/selector.cli  | 31 ++++++++
 examples/pipeline/examples/selector.spec | 95 ++++++++++++++++++++++++
 examples/pipeline/examples/selector.txt  |  4 +
 3 files changed, 130 insertions(+)
 create mode 100644 examples/pipeline/examples/selector.cli
 create mode 100644 examples/pipeline/examples/selector.spec
 create mode 100644 examples/pipeline/examples/selector.txt

diff --git a/examples/pipeline/examples/selector.cli b/examples/pipeline/examples/selector.cli
new file mode 100644
index 000000000..36f3ead54
--- /dev/null
+++ b/examples/pipeline/examples/selector.cli
@@ -0,0 +1,31 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0
+
+link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on
+
+pipeline PIPELINE0 create 0
+
+pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32
+pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32
+pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32
+pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32
+
+pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32
+pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32
+pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32
+pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32
+pipeline PIPELINE0 port out 4 sink none
+
+pipeline PIPELINE0 build ./examples/pipeline/examples/selector.spec
+
+pipeline PIPELINE0 selector s group add
+pipeline PIPELINE0 selector s group member add ./examples/pipeline/examples/selector.txt
+pipeline PIPELINE0 commit
+pipeline PIPELINE0 selector s show
+
+thread 1 pipeline PIPELINE0 enable
diff --git a/examples/pipeline/examples/selector.spec b/examples/pipeline/examples/selector.spec
new file mode 100644
index 000000000..72e49cb1d
--- /dev/null
+++ b/examples/pipeline/examples/selector.spec
@@ -0,0 +1,95 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright(c) 2020 Intel Corporation
+
+; A selector table is made out of groups of weighted members, with a given member potentially part
+; of several groups. The select operation returns a member ID by first selecting a group based on an
+; input group ID and then selecting a member within that group by hashing one or several input
+; header or meta-data fields. It is very useful for implementing an Equal-Cost Multi-Path (ECMP) or
+; Weighted-Cost Multi-Path (WCMP) enabled FIB or a load balancer. It is part of the action selector
+; construct described by the P4 Portable Switch Architecture (PSA) specification.
+;
+; Normally, an action selector FIB is built with a routing table (the base table), a selector table
+; (the group table) and a next hop table (the member table). One of the routing table actions sets
+; up the group ID meta-data field used as the index into the group table, which produces the member
+; ID meta-data field, i.e. the next hop ID that is used as the index into the next hop table. The
+; next hop action prepares the output packet for being sent next hop in the network by prepending
+; one or several headers to the packet (Ethernet at the very least), decrementing the TTL and
+; recomputing the IPv4 checksum, etc. The selector allows for multiple next hops to be specified
+; for any given route as opposed to a single next hop per route; for every packet, its next hop is
+; picked out of the set of next hops defined for the route while preserving the packet ordering
+; within the flow, with the flow defined by the selector n-tuple fields.
+;
+; In this simple example, the base table and the member table are striped out in order to focus
+; exclusively on illustrating the selector table. The group_id is read from the destination MAC
+; address and the selector n-tuple is represented by the Protocol, the source IP address and the
+; destination IP address fields. The member_id produced by the selector table is used to identify
+; the output port which facilitates the testing of different member weights by simply comparing the
+; rates of output packets sent on different ports.
+
+//
+// Headers
+//
+struct ethernet_h {
+	bit<48> dst_addr
+	bit<48> src_addr
+	bit<16> ethertype
+}
+
+struct ipv4_h {
+	bit<8> ver_ihl
+	bit<8> diffserv
+	bit<16> total_len
+	bit<16> identification
+	bit<16> flags_offset
+	bit<8> ttl
+	bit<8> protocol
+	bit<16> hdr_checksum
+	bit<32> src_addr
+	bit<32> dst_addr
+}
+
+header ethernet instanceof ethernet_h
+header ipv4 instanceof ipv4_h
+
+//
+// Meta-data
+//
+struct metadata_t {
+	bit<32> port_in
+	bit<32> port_out
+	bit<32> group_id
+}
+
+metadata instanceof metadata_t
+
+//
+// Selectors.
+//
+selector s {
+	group_id m.group_id
+
+	selector {
+		h.ipv4.protocol
+		h.ipv4.src_addr
+		h.ipv4.dst_addr
+	}
+
+	member_id m.port_out
+
+	n_groups_max 64
+	n_members_per_group_max 16
+}
+
+//
+// Pipeline.
+//
+apply {
+	rx m.port_in
+	extract h.ethernet
+	extract h.ipv4
+	mov m.group_id h.ethernet.dst_addr
+	table s
+	emit h.ethernet
+	emit h.ipv4
+	tx m.port_out
+}
diff --git a/examples/pipeline/examples/selector.txt b/examples/pipeline/examples/selector.txt
new file mode 100644
index 000000000..b3c83c773
--- /dev/null
+++ b/examples/pipeline/examples/selector.txt
@@ -0,0 +1,4 @@
+group 0 member 0 weight 1
+group 0 member 1 weight 1
+group 0 member 2 weight 2
+group 0 member 3 weight 4
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands
  2021-07-09 22:13       ` Thomas Monjalon
@ 2021-07-10  0:26         ` Dumitrescu, Cristian
  0 siblings, 0 replies; 24+ messages in thread
From: Dumitrescu, Cristian @ 2021-07-10  0:26 UTC (permalink / raw)
  To: Thomas Monjalon; +Cc: dev, Khangar, Churchill



> -----Original Message-----
> From: Thomas Monjalon <thomas@monjalon.net>
> Sent: Friday, July 9, 2021 11:14 PM
> To: Dumitrescu, Cristian <cristian.dumitrescu@intel.com>
> Cc: dev@dpdk.org; Khangar, Churchill <churchill.khangar@intel.com>
> Subject: Re: [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table
> update CLI commands
> 
> 09/07/2021 23:37, Thomas Monjalon:
> > 03/07/2021 00:46, Cristian Dumitrescu:
> > > From: Churchill Khangar <churchill.khangar@intel.com>
> > >
> > > For more felxibility, the single monolithic table update command is
> > > split into table entry add, table entry delete, table default entry
> > > add, pipeline commit and pipeline abort.
> > >
> > > Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> > > Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> >
> > Series applied, thanks.
> 
> Sorry, only first 2 patches are really kept,
> because the patch 3 fails 32-bit compilation:
> 
> lib/pipeline/rte_swx_pipeline.c:9851:33: error:
> array subscript ‘struct rte_swx_table_selector_params[0]’
> is partly outside array bounds of ‘unsigned char[24]’ [-Werror=array-bounds]
> 
> 

Hi Thomas,

I just set V4 for this patch set (and the entire series); it was basically just a typo that created a small memory allocation bug: the code was incorrectly using sizeof(struct rte_swx_pipeline_selector_params) instead of sizeof(struct rte_swx_table_selector_params) for the calloc call, hence the warning.

I know it is very late at night right now and you are still awake working to get RC1 done (I am just 1 hour before you time zone wise ;)), I would appreciate if you could still look at applying this for RC1, only if possible.

Thank you so much!

Regards,
Cristian

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V4 3/5] pipeline: add support for selector tables
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 3/5] pipeline: " Cristian Dumitrescu
@ 2021-07-10  6:27           ` Thomas Monjalon
  2021-07-10  9:45             ` Dumitrescu, Cristian
  0 siblings, 1 reply; 24+ messages in thread
From: Thomas Monjalon @ 2021-07-10  6:27 UTC (permalink / raw)
  To: Cristian Dumitrescu; +Cc: dev

10/07/2021 02:20, Cristian Dumitrescu:
> Add pipeline-level support for selector tables,
> 
> Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> ---
> Change log:
> 
> V3 -> V4: Fixed proper name of allocated structure
> (rte_swx_table_selector_params instead of rte_swx_pipeline_selector_params).

Applied (with next patches), thanks.




^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V4 3/5] pipeline: add support for selector tables
  2021-07-10  6:27           ` Thomas Monjalon
@ 2021-07-10  9:45             ` Dumitrescu, Cristian
  0 siblings, 0 replies; 24+ messages in thread
From: Dumitrescu, Cristian @ 2021-07-10  9:45 UTC (permalink / raw)
  To: Thomas Monjalon; +Cc: dev



> -----Original Message-----
> From: Thomas Monjalon <thomas@monjalon.net>
> Sent: Saturday, July 10, 2021 7:28 AM
> To: Dumitrescu, Cristian <cristian.dumitrescu@intel.com>
> Cc: dev@dpdk.org
> Subject: Re: [dpdk-dev] [PATCH V4 3/5] pipeline: add support for selector
> tables
> 
> 10/07/2021 02:20, Cristian Dumitrescu:
> > Add pipeline-level support for selector tables,
> >
> > Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> > ---
> > Change log:
> >
> > V3 -> V4: Fixed proper name of allocated structure
> > (rte_swx_table_selector_params instead of
> rte_swx_pipeline_selector_params).
> 
> Applied (with next patches), thanks.
> 
> 

Thanks very much, Thomas, really appreciate your help!

Regards,
Cristian

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for selector tables
  2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 4/5] examples/pipeline: " Cristian Dumitrescu
@ 2021-07-11 11:27           ` Ali Alnubani
  2021-07-12  7:51             ` Ali Alnubani
  0 siblings, 1 reply; 24+ messages in thread
From: Ali Alnubani @ 2021-07-11 11:27 UTC (permalink / raw)
  To: Cristian Dumitrescu, dev, NBU-Contact-Thomas Monjalon; +Cc: Churchill Khangar

Hi,

> -----Original Message-----
> From: dev <dev-bounces@dpdk.org> On Behalf Of Cristian Dumitrescu
> Sent: Saturday, July 10, 2021 3:21 AM
> To: dev@dpdk.org; NBU-Contact-Thomas Monjalon
> <thomas@monjalon.net>
> Cc: Churchill Khangar <churchill.khangar@intel.com>
> Subject: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for
> selector tables
> 
> Add application-evel support for selector tables.
> 
> Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> ---

This patch is causing the following build error in Ubuntu 16.04.7 with gcc version 5.4.0:
"""
$ meson --werror --buildtype=debugoptimized -Dexamples=all build && ninja -C build
...
...
../../root/dpdk/examples/pipeline/cli.c: In function 'pipeline_selector_group_member_read':
../../root/dpdk/examples/pipeline/cli.c:1559:11: error: 'weight_val' may be used uninitialized in this function [-Werror=maybe-uninitialized]
   *weight = weight_val;
"""

Regards,
Ali

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for selector tables
  2021-07-11 11:27           ` Ali Alnubani
@ 2021-07-12  7:51             ` Ali Alnubani
  2021-07-12  8:09               ` Dumitrescu, Cristian
  0 siblings, 1 reply; 24+ messages in thread
From: Ali Alnubani @ 2021-07-12  7:51 UTC (permalink / raw)
  To: Cristian Dumitrescu, dev, NBU-Contact-Thomas Monjalon; +Cc: Churchill Khangar

> -----Original Message-----
> From: dev <dev-bounces@dpdk.org> On Behalf Of Ali Alnubani
> Sent: Sunday, July 11, 2021 2:27 PM
> To: Cristian Dumitrescu <cristian.dumitrescu@intel.com>; dev@dpdk.org;
> NBU-Contact-Thomas Monjalon <thomas@monjalon.net>
> Cc: Churchill Khangar <churchill.khangar@intel.com>
> Subject: Re: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for
> selector tables
> 
> Hi,
> 
> > -----Original Message-----
> > From: dev <dev-bounces@dpdk.org> On Behalf Of Cristian Dumitrescu
> > Sent: Saturday, July 10, 2021 3:21 AM
> > To: dev@dpdk.org; NBU-Contact-Thomas Monjalon
> <thomas@monjalon.net>
> > Cc: Churchill Khangar <churchill.khangar@intel.com>
> > Subject: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for
> > selector tables
> >
> > Add application-evel support for selector tables.
> >
> > Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> > Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> > ---
> 
> This patch is causing the following build error in Ubuntu 16.04.7 with gcc
> version 5.4.0:
> """
> $ meson --werror --buildtype=debugoptimized -Dexamples=all build &&
> ninja -C build ...
> ...
> ../../root/dpdk/examples/pipeline/cli.c: In function
> 'pipeline_selector_group_member_read':
> ../../root/dpdk/examples/pipeline/cli.c:1559:11: error: 'weight_val' may be
> used uninitialized in this function [-Werror=maybe-uninitialized]
>    *weight = weight_val;
> """

Resolved by https://patches.dpdk.org/project/dpdk/patch/20210712074636.406903-1-alialnu@nvidia.com/.

Regards,
Ali

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for selector tables
  2021-07-12  7:51             ` Ali Alnubani
@ 2021-07-12  8:09               ` Dumitrescu, Cristian
  0 siblings, 0 replies; 24+ messages in thread
From: Dumitrescu, Cristian @ 2021-07-12  8:09 UTC (permalink / raw)
  To: Ali Alnubani, dev, NBU-Contact-Thomas Monjalon; +Cc: Khangar, Churchill



> -----Original Message-----
> From: Ali Alnubani <alialnu@nvidia.com>
> Sent: Monday, July 12, 2021 8:51 AM
> To: Dumitrescu, Cristian <cristian.dumitrescu@intel.com>; dev@dpdk.org;
> NBU-Contact-Thomas Monjalon <thomas@monjalon.net>
> Cc: Khangar, Churchill <churchill.khangar@intel.com>
> Subject: RE: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for
> selector tables
> 
> > -----Original Message-----
> > From: dev <dev-bounces@dpdk.org> On Behalf Of Ali Alnubani
> > Sent: Sunday, July 11, 2021 2:27 PM
> > To: Cristian Dumitrescu <cristian.dumitrescu@intel.com>; dev@dpdk.org;
> > NBU-Contact-Thomas Monjalon <thomas@monjalon.net>
> > Cc: Churchill Khangar <churchill.khangar@intel.com>
> > Subject: Re: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support
> for
> > selector tables
> >
> > Hi,
> >
> > > -----Original Message-----
> > > From: dev <dev-bounces@dpdk.org> On Behalf Of Cristian Dumitrescu
> > > Sent: Saturday, July 10, 2021 3:21 AM
> > > To: dev@dpdk.org; NBU-Contact-Thomas Monjalon
> > <thomas@monjalon.net>
> > > Cc: Churchill Khangar <churchill.khangar@intel.com>
> > > Subject: [dpdk-dev] [PATCH V4 4/5] examples/pipeline: add support for
> > > selector tables
> > >
> > > Add application-evel support for selector tables.
> > >
> > > Signed-off-by: Churchill Khangar <churchill.khangar@intel.com>
> > > Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
> > > ---
> >
> > This patch is causing the following build error in Ubuntu 16.04.7 with gcc
> > version 5.4.0:
> > """
> > $ meson --werror --buildtype=debugoptimized -Dexamples=all build &&
> > ninja -C build ...
> > ...
> > ../../root/dpdk/examples/pipeline/cli.c: In function
> > 'pipeline_selector_group_member_read':
> > ../../root/dpdk/examples/pipeline/cli.c:1559:11: error: 'weight_val' may be
> > used uninitialized in this function [-Werror=maybe-uninitialized]
> >    *weight = weight_val;
> > """
> 
> Resolved by
> https://patches.dpdk.org/project/dpdk/patch/20210712074636.406903-1-
> alialnu@nvidia.com/.
> 
> Regards,
> Ali

Thanks, Ali, for quickly sending the patch to fix this issue!

Regards,
Cristian

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2021-07-12  8:09 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-02 20:49 [dpdk-dev] [PATCH 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 " Cristian Dumitrescu
2021-07-02 22:46   ` [dpdk-dev] [PATCH V3 " Cristian Dumitrescu
2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 2/5] table: add support for selector tables Cristian Dumitrescu
2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 3/5] pipeline: " Cristian Dumitrescu
2021-07-10  0:20       ` [dpdk-dev] [PATCH V4 1/5] examples/pipeline: improve table update CLI commands Cristian Dumitrescu
2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 2/5] table: add support for selector tables Cristian Dumitrescu
2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 3/5] pipeline: " Cristian Dumitrescu
2021-07-10  6:27           ` Thomas Monjalon
2021-07-10  9:45             ` Dumitrescu, Cristian
2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 4/5] examples/pipeline: " Cristian Dumitrescu
2021-07-11 11:27           ` Ali Alnubani
2021-07-12  7:51             ` Ali Alnubani
2021-07-12  8:09               ` Dumitrescu, Cristian
2021-07-10  0:20         ` [dpdk-dev] [PATCH V4 5/5] examples/pipeline: add selector example Cristian Dumitrescu
2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 4/5] examples/pipeline: add support for selector tables Cristian Dumitrescu
2021-07-02 22:46     ` [dpdk-dev] [PATCH V3 5/5] examples/pipeline: add selector example Cristian Dumitrescu
2021-07-09 21:37     ` [dpdk-dev] [PATCH V3 1/5] examples/pipeline: improve table update CLI commands Thomas Monjalon
2021-07-09 22:13       ` Thomas Monjalon
2021-07-10  0:26         ` Dumitrescu, Cristian
2021-07-02 22:39 ` [dpdk-dev] [PATCH V2 2/5] table: add support for selector tables Cristian Dumitrescu
2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 3/5] pipeline: " Cristian Dumitrescu
2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 4/5] examples/pipeline: " Cristian Dumitrescu
2021-07-02 22:40 ` [dpdk-dev] [PATCH V2 5/5] examples/pipeline: add selector example Cristian Dumitrescu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.