[v2,32/41] pipeline: add SWX pipeline specification file

Message ID 20200907214032.95052-33-cristian.dumitrescu@intel.com (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series Pipeline alignment with the P4 language |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Cristian Dumitrescu Sept. 7, 2020, 9:40 p.m. UTC
  Add support for building the SWX pipeline based on specification file
with syntax aligned to the P4 language. The specification file may be
generated by the P4C compiler in the future.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/librte_pipeline/Makefile                 |    1 +
 lib/librte_pipeline/meson.build              |    1 +
 lib/librte_pipeline/rte_pipeline_version.map |    1 +
 lib/librte_pipeline/rte_swx_pipeline.h       |   26 +
 lib/librte_pipeline/rte_swx_pipeline_spec.c  | 1439 ++++++++++++++++++
 5 files changed, 1468 insertions(+)
 create mode 100644 lib/librte_pipeline/rte_swx_pipeline_spec.c
  

Patch

diff --git a/lib/librte_pipeline/Makefile b/lib/librte_pipeline/Makefile
index d4a5f77f7..8f0058fb7 100644
--- a/lib/librte_pipeline/Makefile
+++ b/lib/librte_pipeline/Makefile
@@ -22,6 +22,7 @@  SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) := rte_pipeline.c
 SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) += rte_port_in_action.c
 SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) += rte_table_action.c
 SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) += rte_swx_pipeline.c
+SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) += rte_swx_pipeline_spec.c
 SRCS-$(CONFIG_RTE_LIBRTE_PIPELINE) += rte_swx_ctl.c
 
 # install includes
diff --git a/lib/librte_pipeline/meson.build b/lib/librte_pipeline/meson.build
index be1d9c3a4..65c1a8d6a 100644
--- a/lib/librte_pipeline/meson.build
+++ b/lib/librte_pipeline/meson.build
@@ -5,6 +5,7 @@  sources = files('rte_pipeline.c',
 	'rte_port_in_action.c',
 	'rte_table_action.c',
 	'rte_swx_pipeline.c',
+	'rte_swx_pipeline_spec.c',
 	'rte_swx_ctl.c',)
 headers = files('rte_pipeline.h',
 	'rte_port_in_action.h',
diff --git a/lib/librte_pipeline/rte_pipeline_version.map b/lib/librte_pipeline/rte_pipeline_version.map
index ec38f0eef..2cb50d571 100644
--- a/lib/librte_pipeline/rte_pipeline_version.map
+++ b/lib/librte_pipeline/rte_pipeline_version.map
@@ -72,6 +72,7 @@  EXPERIMENTAL {
 	rte_swx_pipeline_table_config;
 	rte_swx_pipeline_instructions_config;
 	rte_swx_pipeline_build;
+	rte_swx_pipeline_build_from_spec;
 	rte_swx_pipeline_free;
 	rte_swx_pipeline_run;
 	rte_swx_pipeline_flush;
diff --git a/lib/librte_pipeline/rte_swx_pipeline.h b/lib/librte_pipeline/rte_swx_pipeline.h
index 6da5710af..6928e78b6 100644
--- a/lib/librte_pipeline/rte_swx_pipeline.h
+++ b/lib/librte_pipeline/rte_swx_pipeline.h
@@ -643,6 +643,32 @@  __rte_experimental
 int
 rte_swx_pipeline_build(struct rte_swx_pipeline *p);
 
+/**
+ * Pipeline build from specification file
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] spec
+ *   Pipeline specification file.
+ * @param[out] err_line
+ *   In case of error and non-NULL, the line number within the *spec* file where
+ *   the error occurred. The first line number in the file is 1.
+ * @param[out] err_msg
+ *   In case of error and non-NULL, the error message.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough space/cannot allocate memory;
+ *   -EEXIST: Resource with the same name already exists;
+ *   -ENODEV: Extern object or table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
+				 FILE *spec,
+				 uint32_t *err_line,
+				 const char **err_msg);
+
 /**
  * Pipeline run
  *
diff --git a/lib/librte_pipeline/rte_swx_pipeline_spec.c b/lib/librte_pipeline/rte_swx_pipeline_spec.c
new file mode 100644
index 000000000..d72badd03
--- /dev/null
+++ b/lib/librte_pipeline/rte_swx_pipeline_spec.c
@@ -0,0 +1,1439 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Intel Corporation
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "rte_swx_pipeline.h"
+#include "rte_swx_ctl.h"
+
+#define MAX_LINE_LENGTH 256
+#define MAX_TOKENS 16
+#define MAX_INSTRUCTION_LENGTH 256
+
+#define STRUCT_BLOCK 0
+#define ACTION_BLOCK 1
+#define TABLE_BLOCK 2
+#define TABLE_KEY_BLOCK 3
+#define TABLE_ACTIONS_BLOCK 4
+#define APPLY_BLOCK 5
+
+/*
+ * extobj.
+ *
+ * extobj OBJ_NAME instanceof OBJ_TYPE [ pragma OBJ_CREATE_ARGS ]
+ */
+struct extobj_spec {
+	char *name;
+	char *extern_type_name;
+	char *pragma;
+};
+
+static void
+extobj_spec_free(struct extobj_spec *s)
+{
+	free(s->name);
+	free(s->extern_type_name);
+	free(s->pragma);
+}
+
+static int
+extobj_statement_parse(struct extobj_spec *s,
+		       char **tokens,
+		       uint32_t n_tokens,
+		       uint32_t n_lines,
+		       uint32_t *err_line,
+		       const char **err_msg)
+{
+	/* Check format. */
+	if (((n_tokens != 4) && (n_tokens != 6)) ||
+	    ((n_tokens == 4) && strcmp(tokens[2], "instanceof")) ||
+	    ((n_tokens == 6) && (strcmp(tokens[2], "instanceof") ||
+				 strcmp(tokens[4], "pragma")))) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid extobj statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	s->extern_type_name = strdup(tokens[3]);
+	s->pragma = (n_tokens == 6) ? strdup(tokens[5]) : NULL;
+
+	if (!s->name ||
+	    !s->extern_type_name ||
+	    ((n_tokens == 6) && !s->pragma)) {
+		free(s->name);
+		free(s->extern_type_name);
+		free(s->pragma);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * struct.
+ *
+ * struct STRUCT_TYPE_NAME {
+ *	bit<SIZE> FIELD_NAME
+ *	...
+ * }
+ */
+struct struct_spec {
+	char *name;
+	struct rte_swx_field_params *fields;
+	uint32_t n_fields;
+};
+
+static void
+struct_spec_free(struct struct_spec *s)
+{
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	free(s->name);
+	s->name = NULL;
+
+	for (i = 0; i < s->n_fields; i++) {
+		uintptr_t name = (uintptr_t)s->fields[i].name;
+
+		free((void *)name);
+	}
+
+	free(s->fields);
+	s->fields = NULL;
+
+	s->n_fields = 0;
+}
+
+static int
+struct_statement_parse(struct struct_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 struct 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 << STRUCT_BLOCK;
+
+	return 0;
+}
+
+static int
+struct_block_parse(struct struct_spec *s,
+		   uint32_t *block_mask,
+		   char **tokens,
+		   uint32_t n_tokens,
+		   uint32_t n_lines,
+		   uint32_t *err_line,
+		   const char **err_msg)
+{
+	struct rte_swx_field_params *new_fields;
+	char *p = tokens[0], *name;
+	uint32_t n_bits;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << STRUCT_BLOCK);
+		return 0;
+	}
+
+	/* Check format. */
+	if ((n_tokens != 2) ||
+	    (strlen(p) < 6) ||
+	    (p[0] != 'b') ||
+	    (p[1] != 'i') ||
+	    (p[2] != 't') ||
+	    (p[3] != '<') ||
+	    (p[strlen(p) - 1] != '>')) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid struct field statement.";
+		return -EINVAL;
+	}
+
+	/* Remove the "bit<" and ">". */
+	p[strlen(p) - 1] = 0;
+	p += 4;
+
+	n_bits = strtoul(p, &p, 0);
+	if ((p[0]) ||
+	    !n_bits ||
+	    (n_bits % 8) ||
+	    (n_bits > 64)) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid struct field size.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	name = strdup(tokens[1]);
+	if (!name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_fields = reallocarray(s->fields,
+				  s->n_fields + 1,
+				  sizeof(struct rte_swx_field_params));
+	if (!new_fields) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->fields = new_fields;
+	s->fields[s->n_fields].name = name;
+	s->fields[s->n_fields].n_bits = n_bits;
+	s->n_fields++;
+
+	return 0;
+}
+
+/*
+ * header.
+ *
+ * header HEADER_NAME instanceof STRUCT_TYPE_NAME
+ */
+struct header_spec {
+	char *name;
+	char *struct_type_name;
+};
+
+static void
+header_spec_free(struct header_spec *s)
+{
+	free(s->name);
+	free(s->struct_type_name);
+}
+
+static int
+header_statement_parse(struct header_spec *s,
+		       char **tokens,
+		       uint32_t n_tokens,
+		       uint32_t n_lines,
+		       uint32_t *err_line,
+		       const char **err_msg)
+{
+	/* Check format. */
+	if ((n_tokens != 4) || strcmp(tokens[2], "instanceof")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid header statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	s->struct_type_name = strdup(tokens[3]);
+
+	if (!s->name || !s->struct_type_name) {
+		free(s->name);
+		free(s->struct_type_name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * metadata.
+ *
+ * metadata instanceof STRUCT_TYPE_NAME
+ */
+struct metadata_spec {
+	char *struct_type_name;
+};
+
+static void
+metadata_spec_free(struct metadata_spec *s)
+{
+	free(s->struct_type_name);
+}
+
+static int
+metadata_statement_parse(struct metadata_spec *s,
+			 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[1], "instanceof")) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid metadata statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->struct_type_name = strdup(tokens[2]);
+	if (!s->struct_type_name) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * action.
+ *
+ * action ACTION_NAME args none | instanceof STRUCT_TYPE_NAME {
+ *	INSTRUCTION
+ *	...
+ * }
+ */
+struct action_spec {
+	char *name;
+	char *args_struct_type_name;
+	const char **instructions;
+	uint32_t n_instructions;
+};
+
+static void
+action_spec_free(struct action_spec *s)
+{
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	free(s->name);
+	s->name = NULL;
+
+	free(s->args_struct_type_name);
+	s->args_struct_type_name = NULL;
+
+	for (i = 0; i < s->n_instructions; i++) {
+		uintptr_t instr = (uintptr_t)s->instructions[i];
+
+		free((void *)instr);
+	}
+
+	free(s->instructions);
+	s->instructions = NULL;
+
+	s->n_instructions = 0;
+}
+
+static int
+action_statement_parse(struct action_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 != 5) && (n_tokens != 6)) ||
+	    ((n_tokens == 5) &&
+	     (strcmp(tokens[2], "args") ||
+	      strcmp(tokens[3], "none") ||
+	      strcmp(tokens[4], "{"))) ||
+	    ((n_tokens == 6) &&
+	     (strcmp(tokens[2], "args") ||
+	      strcmp(tokens[3], "instanceof") ||
+	      strcmp(tokens[5], "{")))) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid action statement.";
+		return -EINVAL;
+	}
+
+	/* spec. */
+	s->name = strdup(tokens[1]);
+	s->args_struct_type_name = (n_tokens == 6) ? strdup(tokens[4]) : NULL;
+
+	if ((!s->name) || ((n_tokens == 6) && !s->args_struct_type_name)) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << ACTION_BLOCK;
+
+	return 0;
+}
+
+static int
+action_block_parse(struct action_spec *s,
+		   uint32_t *block_mask,
+		   char **tokens,
+		   uint32_t n_tokens,
+		   uint32_t n_lines,
+		   uint32_t *err_line,
+		   const char **err_msg)
+{
+	char buffer[MAX_INSTRUCTION_LENGTH], *instr;
+	const char **new_instructions;
+	uint32_t i;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << ACTION_BLOCK);
+		return 0;
+	}
+
+	/* spec. */
+	buffer[0] = 0;
+	for (i = 0; i < n_tokens; i++) {
+		if (i)
+			strcat(buffer, " ");
+		strcat(buffer, tokens[i]);
+	}
+
+	instr = strdup(buffer);
+	if (!instr) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_instructions = reallocarray(s->instructions,
+					s->n_instructions + 1,
+					sizeof(char *));
+	if (!new_instructions) {
+		free(instr);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->instructions = new_instructions;
+	s->instructions[s->n_instructions] = instr;
+	s->n_instructions++;
+
+	return 0;
+}
+
+/*
+ * table.
+ *
+ * table {
+ *	key {
+ *		MATCH_FIELD_NAME exact | wildcard | lpm
+ *		...
+ *	}
+ *	actions {
+ *		ACTION_NAME
+ *		...
+ *	}
+ *	default_action ACTION_NAME args none | ARGS_BYTE_ARRAY [ const ]
+ *	instanceof TABLE_TYPE_NAME
+ *	pragma ARGS
+ *	size SIZE
+ * }
+ */
+struct table_spec {
+	char *name;
+	struct rte_swx_pipeline_table_params params;
+	char *recommended_table_type_name;
+	char *args;
+	uint32_t size;
+};
+
+static void
+table_spec_free(struct table_spec *s)
+{
+	uintptr_t default_action_name;
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	free(s->name);
+	s->name = NULL;
+
+	for (i = 0; i < s->params.n_fields; i++) {
+		uintptr_t name = (uintptr_t)s->params.fields[i].name;
+
+		free((void *)name);
+	}
+
+	free(s->params.fields);
+	s->params.fields = NULL;
+
+	s->params.n_fields = 0;
+
+	for (i = 0; i < s->params.n_actions; i++) {
+		uintptr_t name = (uintptr_t)s->params.action_names[i];
+
+		free((void *)name);
+	}
+
+	free(s->params.action_names);
+	s->params.action_names = NULL;
+
+	s->params.n_actions = 0;
+
+	default_action_name = (uintptr_t)s->params.default_action_name;
+	free((void *)default_action_name);
+	s->params.default_action_name = NULL;
+
+	free(s->params.default_action_data);
+	s->params.default_action_data = NULL;
+
+	s->params.default_action_is_const = 0;
+
+	free(s->recommended_table_type_name);
+	s->recommended_table_type_name = NULL;
+
+	free(s->args);
+	s->args = NULL;
+
+	s->size = 0;
+}
+
+static int
+table_key_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 key statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << TABLE_KEY_BLOCK;
+
+	return 0;
+}
+
+static int
+table_key_block_parse(struct table_spec *s,
+		      uint32_t *block_mask,
+		      char **tokens,
+		      uint32_t n_tokens,
+		      uint32_t n_lines,
+		      uint32_t *err_line,
+		      const char **err_msg)
+{
+	struct rte_swx_match_field_params *new_fields;
+	enum rte_swx_table_match_type match_type = RTE_SWX_TABLE_MATCH_WILDCARD;
+	char *name;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << TABLE_KEY_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if ((n_tokens != 2) ||
+	    (strcmp(tokens[1], "exact") &&
+	     strcmp(tokens[1], "wildcard") &&
+	     strcmp(tokens[1], "lpm"))) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid match field statement.";
+		return -EINVAL;
+	}
+
+	if (!strcmp(tokens[1], "wildcard"))
+		match_type = RTE_SWX_TABLE_MATCH_WILDCARD;
+	if (!strcmp(tokens[1], "lpm"))
+		match_type = RTE_SWX_TABLE_MATCH_LPM;
+	if (!strcmp(tokens[1], "exact"))
+		match_type = RTE_SWX_TABLE_MATCH_EXACT;
+
+	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 = reallocarray(s->params.fields,
+				  s->params.n_fields + 1,
+				  sizeof(struct rte_swx_match_field_params));
+	if (!new_fields) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.fields = new_fields;
+	s->params.fields[s->params.n_fields].name = name;
+	s->params.fields[s->params.n_fields].match_type = match_type;
+	s->params.n_fields++;
+
+	return 0;
+}
+
+static int
+table_actions_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 actions statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << TABLE_ACTIONS_BLOCK;
+
+	return 0;
+}
+
+static int
+table_actions_block_parse(struct table_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_action_names;
+	char *name;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << TABLE_ACTIONS_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Invalid action name 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_action_names = reallocarray(s->params.action_names,
+					s->params.n_actions + 1,
+					sizeof(char *));
+	if (!new_action_names) {
+		free(name);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->params.action_names = new_action_names;
+	s->params.action_names[s->params.n_actions] = name;
+	s->params.n_actions++;
+
+	return 0;
+}
+
+static int
+table_statement_parse(struct table_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 table 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 << TABLE_BLOCK;
+
+	return 0;
+}
+
+static int
+table_block_parse(struct table_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 << TABLE_KEY_BLOCK))
+		return table_key_block_parse(s,
+					     block_mask,
+					     tokens,
+					     n_tokens,
+					     n_lines,
+					     err_line,
+					     err_msg);
+
+	if (*block_mask & (1 << TABLE_ACTIONS_BLOCK))
+		return table_actions_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 << TABLE_BLOCK);
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "key"))
+		return table_key_statement_parse(block_mask,
+						 tokens,
+						 n_tokens,
+						 n_lines,
+						 err_line,
+						 err_msg);
+
+	if (!strcmp(tokens[0], "actions"))
+		return table_actions_statement_parse(block_mask,
+						     tokens,
+						     n_tokens,
+						     n_lines,
+						     err_line,
+						     err_msg);
+
+	if (!strcmp(tokens[0], "default_action")) {
+		if (((n_tokens != 4) && (n_tokens != 5)) ||
+		    strcmp(tokens[2], "args") ||
+		    strcmp(tokens[3], "none") ||
+		    ((n_tokens == 5) && strcmp(tokens[4], "const"))) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid default_action statement.";
+			return -EINVAL;
+		}
+
+		if (s->params.default_action_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Duplicate default_action stmt.";
+			return -EINVAL;
+		}
+
+		s->params.default_action_name = strdup(tokens[1]);
+		if (!s->params.default_action_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		if (n_tokens == 5)
+			s->params.default_action_is_const = 1;
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "instanceof")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid instanceof statement.";
+			return -EINVAL;
+		}
+
+		if (s->recommended_table_type_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Duplicate instanceof statement.";
+			return -EINVAL;
+		}
+
+		s->recommended_table_type_name = strdup(tokens[1]);
+		if (!s->recommended_table_type_name) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "pragma")) {
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid pragma statement.";
+			return -EINVAL;
+		}
+
+		if (s->args) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Duplicate pragma statement.";
+			return -EINVAL;
+		}
+
+		s->args = strdup(tokens[1]);
+		if (!s->args) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Memory allocation failed.";
+			return -ENOMEM;
+		}
+
+		return 0;
+	}
+
+	if (!strcmp(tokens[0], "size")) {
+		char *p = tokens[1];
+
+		if (n_tokens != 2) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid pragma statement.";
+			return -EINVAL;
+		}
+
+		s->size = strtoul(p, &p, 0);
+		if (p[0]) {
+			if (err_line)
+				*err_line = n_lines;
+			if (err_msg)
+				*err_msg = "Invalid size argument.";
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* Anything else. */
+	if (err_line)
+		*err_line = n_lines;
+	if (err_msg)
+		*err_msg = "Invalid statement.";
+	return -EINVAL;
+}
+
+/*
+ * apply.
+ *
+ * apply {
+ *	INSTRUCTION
+ *	...
+ * }
+ */
+struct apply_spec {
+	const char **instructions;
+	uint32_t n_instructions;
+};
+
+static void
+apply_spec_free(struct apply_spec *s)
+{
+	uint32_t i;
+
+	if (!s)
+		return;
+
+	for (i = 0; i < s->n_instructions; i++) {
+		uintptr_t instr = (uintptr_t)s->instructions[i];
+
+		free((void *)instr);
+	}
+
+	free(s->instructions);
+	s->instructions = NULL;
+
+	s->n_instructions = 0;
+}
+
+static int
+apply_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 apply statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << APPLY_BLOCK;
+
+	return 0;
+}
+
+static int
+apply_block_parse(struct apply_spec *s,
+		  uint32_t *block_mask,
+		  char **tokens,
+		  uint32_t n_tokens,
+		  uint32_t n_lines,
+		  uint32_t *err_line,
+		  const char **err_msg)
+{
+	char buffer[MAX_INSTRUCTION_LENGTH], *instr;
+	const char **new_instructions;
+	uint32_t i;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << APPLY_BLOCK);
+		return 0;
+	}
+
+	/* spec. */
+	buffer[0] = 0;
+	for (i = 0; i < n_tokens; i++) {
+		if (i)
+			strcat(buffer, " ");
+		strcat(buffer, tokens[i]);
+	}
+
+	instr = strdup(buffer);
+	if (!instr) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	new_instructions = reallocarray(s->instructions,
+					s->n_instructions + 1,
+					sizeof(char *));
+	if (!new_instructions) {
+		free(instr);
+
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Memory allocation failed.";
+		return -ENOMEM;
+	}
+
+	s->instructions = new_instructions;
+	s->instructions[s->n_instructions] = instr;
+	s->n_instructions++;
+
+	return 0;
+}
+
+/*
+ * Pipeline.
+ */
+int
+rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
+				 FILE *spec,
+				 uint32_t *err_line,
+				 const char **err_msg)
+{
+	struct extobj_spec extobj_spec = {0};
+	struct struct_spec struct_spec = {0};
+	struct header_spec header_spec = {0};
+	struct metadata_spec metadata_spec = {0};
+	struct action_spec action_spec = {0};
+	struct table_spec table_spec = {0};
+	struct apply_spec apply_spec = {0};
+	uint32_t n_lines;
+	uint32_t block_mask = 0;
+	int status;
+
+	/* Check the input arguments. */
+	if (!p) {
+		if (err_line)
+			*err_line = 0;
+		if (err_msg)
+			*err_msg = "Null pipeline arument.";
+		status = -EINVAL;
+		goto error;
+	}
+
+	if (!p) {
+		if (err_line)
+			*err_line = 0;
+		if (err_msg)
+			*err_msg = "Null specification file argument.";
+		status = -EINVAL;
+		goto error;
+	}
+
+	for (n_lines = 1; ; n_lines++) {
+		char line[MAX_LINE_LENGTH];
+		char *tokens[MAX_TOKENS], *ptr = line;
+		uint32_t n_tokens = 0;
+
+		/* Read next line. */
+		if (!fgets(line, sizeof(line), spec))
+			break;
+
+		/* Parse the line into tokens. */
+		for ( ; ; ) {
+			char *token;
+
+			/* Get token. */
+			token = strtok_r(ptr, " \f\n\r\t\v", &ptr);
+			if (!token)
+				break;
+
+			/* Handle comments. */
+			if ((token[0] == '#') ||
+			    (token[0] == ';') ||
+			    ((token[0] == '/') && (token[1] == '/'))) {
+				break;
+			}
+
+			/* Handle excessively long lines. */
+			if (n_tokens >= MAX_TOKENS) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Too many tokens.";
+				status = -EINVAL;
+				goto error;
+			}
+
+			/* Save token. */
+			tokens[n_tokens] = token;
+			n_tokens++;
+		}
+
+		/* Handle empty lines. */
+		if (!n_tokens)
+			continue;
+
+		/* struct block. */
+		if (block_mask & (1 << STRUCT_BLOCK)) {
+			status = struct_block_parse(&struct_spec,
+						    &block_mask,
+						    tokens,
+						    n_tokens,
+						    n_lines,
+						    err_line,
+						    err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << STRUCT_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_struct_type_register(p,
+				struct_spec.name,
+				struct_spec.fields,
+				struct_spec.n_fields);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Struct registration error.";
+				goto error;
+			}
+
+			struct_spec_free(&struct_spec);
+
+			continue;
+		}
+
+		/* action block. */
+		if (block_mask & (1 << ACTION_BLOCK)) {
+			status = action_block_parse(&action_spec,
+						    &block_mask,
+						    tokens,
+						    n_tokens,
+						    n_lines,
+						    err_line,
+						    err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << ACTION_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_action_config(p,
+				action_spec.name,
+				action_spec.args_struct_type_name,
+				action_spec.instructions,
+				action_spec.n_instructions);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Action config error.";
+				goto error;
+			}
+
+			action_spec_free(&action_spec);
+
+			continue;
+		}
+
+		/* table block. */
+		if (block_mask & (1 << TABLE_BLOCK)) {
+			status = table_block_parse(&table_spec,
+						   &block_mask,
+						   tokens,
+						   n_tokens,
+						   n_lines,
+						   err_line,
+						   err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << TABLE_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_table_config(p,
+				table_spec.name,
+				&table_spec.params,
+				table_spec.recommended_table_type_name,
+				table_spec.args,
+				table_spec.size);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Table configuration error.";
+				goto error;
+			}
+
+			table_spec_free(&table_spec);
+
+			continue;
+		}
+
+		/* apply block. */
+		if (block_mask & (1 << APPLY_BLOCK)) {
+			status = apply_block_parse(&apply_spec,
+						   &block_mask,
+						   tokens,
+						   n_tokens,
+						   n_lines,
+						   err_line,
+						   err_msg);
+			if (status)
+				goto error;
+
+			if (block_mask & (1 << APPLY_BLOCK))
+				continue;
+
+			/* End of block. */
+			status = rte_swx_pipeline_instructions_config(p,
+				apply_spec.instructions,
+				apply_spec.n_instructions);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Pipeline instructions err.";
+				goto error;
+			}
+
+			apply_spec_free(&apply_spec);
+
+			continue;
+		}
+
+		/* extobj. */
+		if (!strcmp(tokens[0], "extobj")) {
+			status = extobj_statement_parse(&extobj_spec,
+							tokens,
+							n_tokens,
+							n_lines,
+							err_line,
+							err_msg);
+			if (status)
+				goto error;
+
+			status = rte_swx_pipeline_extern_object_config(p,
+				extobj_spec.name,
+				extobj_spec.extern_type_name,
+				extobj_spec.pragma);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Extern object config err.";
+				goto error;
+			}
+
+			extobj_spec_free(&extobj_spec);
+
+			continue;
+		}
+
+		/* struct. */
+		if (!strcmp(tokens[0], "struct")) {
+			status = struct_statement_parse(&struct_spec,
+							&block_mask,
+							tokens,
+							n_tokens,
+							n_lines,
+							err_line,
+							err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
+		/* header. */
+		if (!strcmp(tokens[0], "header")) {
+			status = header_statement_parse(&header_spec,
+							tokens,
+							n_tokens,
+							n_lines,
+							err_line,
+							err_msg);
+			if (status)
+				goto error;
+
+			status = rte_swx_pipeline_packet_header_register(p,
+				header_spec.name,
+				header_spec.struct_type_name);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Header registration error.";
+				goto error;
+			}
+
+			header_spec_free(&header_spec);
+
+			continue;
+		}
+
+		/* metadata. */
+		if (!strcmp(tokens[0], "metadata")) {
+			status = metadata_statement_parse(&metadata_spec,
+							  tokens,
+							  n_tokens,
+							  n_lines,
+							  err_line,
+							  err_msg);
+			if (status)
+				goto error;
+
+			status = rte_swx_pipeline_packet_metadata_register(p,
+				metadata_spec.struct_type_name);
+			if (status) {
+				if (err_line)
+					*err_line = n_lines;
+				if (err_msg)
+					*err_msg = "Meta-data reg err.";
+				goto error;
+			}
+
+			metadata_spec_free(&metadata_spec);
+
+			continue;
+		}
+
+		/* action. */
+		if (!strcmp(tokens[0], "action")) {
+			status = action_statement_parse(&action_spec,
+							&block_mask,
+							tokens,
+							n_tokens,
+							n_lines,
+							err_line,
+							err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
+		/* table. */
+		if (!strcmp(tokens[0], "table")) {
+			status = table_statement_parse(&table_spec,
+						       &block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
+		/* apply. */
+		if (!strcmp(tokens[0], "apply")) {
+			status = apply_statement_parse(&block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
+			if (status)
+				goto error;
+
+			continue;
+		}
+
+		/* Anything else. */
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Unknown statement.";
+		status = -EINVAL;
+		goto error;
+	}
+
+	/* Handle unfinished block. */
+	if (block_mask) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Missing }.";
+		status = -EINVAL;
+		goto error;
+	}
+
+	/* Pipeline build. */
+	status = rte_swx_pipeline_build(p);
+	if (status) {
+		if (err_line)
+			*err_line = n_lines;
+		if (err_msg)
+			*err_msg = "Pipeline build error.";
+		goto error;
+	}
+
+	return 0;
+
+error:
+	extobj_spec_free(&extobj_spec);
+	struct_spec_free(&struct_spec);
+	header_spec_free(&header_spec);
+	metadata_spec_free(&metadata_spec);
+	action_spec_free(&action_spec);
+	table_spec_free(&table_spec);
+	apply_spec_free(&apply_spec);
+	return status;
+}