DPDK patches and discussions
 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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).