[PATCH RFC 3/5] Implement DT schema checker using hybrid approach

Tomasz Figa t.figa at samsung.com
Thu Feb 20 13:06:49 EST 2014


This patch adds a proof of concept framework to implement schema checker
using a combined C and DTSS based approach.

Complex and generic bindings can be implemented directly in C and then
instantiated from simple device-specific bindings using DTS-like DTSS
language.

This is based on Stephen Warren's C based DT schema checker proof of
concept patch.

[original C based DT schema checker proof of concept]
Signed-off-by: Stephen Warren <swarren at wwwdotorg.org>
[further development into hybrid solution]
Signed-off-by: Tomasz Figa <t.figa at samsung.com>
---
 Makefile         |   2 +-
 Makefile.dtc     |   5 +-
 checks.c         |  15 +++
 dtc.c            |  17 ++-
 dtc.h            |  26 +++++
 dtss-lexer.l     | 291 +++++++++++++++++++++++++++++++++++++++++++++++
 dtss-parser.y    | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 schemas/schema.c | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++
 schemas/schema.h |  89 +++++++++++++++
 srcpos.h         |   2 +
 treesource.c     |  22 ++++
 11 files changed, 1117 insertions(+), 4 deletions(-)
 create mode 100644 dtss-lexer.l
 create mode 100644 dtss-parser.y
 create mode 100644 schemas/schema.c
 create mode 100644 schemas/schema.h

diff --git a/Makefile b/Makefile
index 86f5ab3..0625fb8 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ EXTRAVERSION =
 LOCAL_VERSION =
 CONFIG_LOCALVERSION =
 
-CPPFLAGS = -I libfdt -I .
+CPPFLAGS = -I libfdt -I . -I schemas
 WARNINGS = -Werror -Wall -Wpointer-arith -Wcast-qual -Wnested-externs \
 	-Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wshadow
 CFLAGS = -g -Os -fPIC -Werror $(WARNINGS)
diff --git a/Makefile.dtc b/Makefile.dtc
index bece49b..bf19564 100644
--- a/Makefile.dtc
+++ b/Makefile.dtc
@@ -12,7 +12,8 @@ DTC_SRCS = \
 	livetree.c \
 	srcpos.c \
 	treesource.c \
-	util.c
+	util.c \
+	schemas/schema.c
 
-DTC_GEN_SRCS = dtc-lexer.lex.c dtc-parser.tab.c
+DTC_GEN_SRCS = dtc-lexer.lex.c dtc-parser.tab.c dtss-lexer.lex.c dtss-parser.tab.c
 DTC_OBJS = $(DTC_SRCS:%.c=%.o) $(DTC_GEN_SRCS:%.c=%.o)
diff --git a/checks.c b/checks.c
index 47eda65..7c85fcf 100644
--- a/checks.c
+++ b/checks.c
@@ -19,6 +19,7 @@
  */
 
 #include "dtc.h"
+#include "schemas/schema.h"
 
 #ifdef TRACE_CHECKS
 #define TRACE(c, ...) \
@@ -651,6 +652,18 @@ static void check_obsolete_chosen_interrupt_controller(struct check *c,
 }
 TREE_WARNING(obsolete_chosen_interrupt_controller, NULL);
 
+/*
+ * Schema checks
+ */
+
+static void check_schema(struct check *c, struct node *dt,
+				       struct node *node)
+{
+	if (schema_check_node(dt, node))
+		FAIL(c, "Schema check failed for %s", node->fullpath);
+}
+NODE_ERROR(schema, NULL);
+
 static struct check *check_table[] = {
 	&duplicate_node_names, &duplicate_property_names,
 	&node_name_chars, &node_name_format, &property_name_chars,
@@ -669,6 +682,8 @@ static struct check *check_table[] = {
 	&avoid_default_addr_size,
 	&obsolete_chosen_interrupt_controller,
 
+	&schema,
+
 	&always_fail,
 };
 
diff --git a/dtc.c b/dtc.c
index d36ccdc..1a5913b 100644
--- a/dtc.c
+++ b/dtc.c
@@ -20,6 +20,7 @@
 
 #include "dtc.h"
 #include "srcpos.h"
+#include "schemas/schema.h"
 
 /*
  * Command line options
@@ -49,7 +50,7 @@ static void fill_fullpaths(struct node *tree, const char *prefix)
 
 /* Usage related data. */
 static const char usage_synopsis[] = "dtc [options] <input file>";
-static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:fb:i:H:sW:E:hv";
+static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:fb:i:H:sW:E:x:hv";
 static struct option const usage_long_opts[] = {
 	{"quiet",            no_argument, NULL, 'q'},
 	{"in-format",         a_argument, NULL, 'I'},
@@ -67,6 +68,7 @@ static struct option const usage_long_opts[] = {
 	{"phandle",           a_argument, NULL, 'H'},
 	{"warning",           a_argument, NULL, 'W'},
 	{"error",             a_argument, NULL, 'E'},
+	{"schema",            a_argument, NULL, 'x'},
 	{"help",             no_argument, NULL, 'h'},
 	{"version",          no_argument, NULL, 'v'},
 	{NULL,               no_argument, NULL, 0x0},
@@ -97,6 +99,7 @@ static const char * const usage_opts_help[] = {
 	 "\t\tboth   - Both \"linux,phandle\" and \"phandle\" properties",
 	"\n\tEnable/disable warnings (prefix with \"no-\")",
 	"\n\tEnable/disable errors (prefix with \"no-\")",
+	"\n\tUse schema file"
 	"\n\tPrint this help and exit",
 	"\n\tPrint version and exit",
 	NULL,
@@ -105,10 +108,12 @@ static const char * const usage_opts_help[] = {
 int main(int argc, char *argv[])
 {
 	struct boot_info *bi;
+	struct boot_info *bi_schema;
 	const char *inform = "dts";
 	const char *outform = "dts";
 	const char *outname = "-";
 	const char *depname = NULL;
+	const char *schema = NULL;
 	bool force = false, sort = false;
 	const char *arg;
 	int opt;
@@ -185,6 +190,10 @@ int main(int argc, char *argv[])
 			parse_checks_option(false, true, optarg);
 			break;
 
+		case 'x':
+			schema = optarg;
+			break;
+
 		case 'h':
 			usage(NULL);
 		default:
@@ -220,6 +229,12 @@ int main(int argc, char *argv[])
 	else
 		die("Unknown input format \"%s\"\n", inform);
 
+	if (schema) {
+		bi_schema = schema_from_source(schema);
+		//dt_to_source(stdout, bi_schema);
+		build_schema_list(bi_schema);
+	}
+
 	if (depfile) {
 		fputc('\n', depfile);
 		fclose(depfile);
diff --git a/dtc.h b/dtc.h
index 9ce9d12..19d2d24 100644
--- a/dtc.h
+++ b/dtc.h
@@ -135,21 +135,43 @@ struct label {
 	struct label *next;
 };
 
+enum {
+	PROPERTY_DATA,
+	PROPERTY_USE,
+	PROPERTY_REQUIRE,
+	PROPERTY_MATCH,
+};
+
+#define PROPERTY_FLAG_OPTIONAL	(1 << 0)
+
 struct property {
 	bool deleted;
 	char *name;
 	struct data val;
+	unsigned int type;
+	unsigned int flags;
 
 	struct property *next;
 
 	struct label *labels;
 };
 
+enum {
+	NODE_DATA,
+	NODE_USE,
+	NODE_REQUIRE,
+};
+
+#define NODE_FLAG_OPTIONAL	(1 << 0)
+#define NODE_FLAG_INCOMPLETE	(1 << 1)
+
 struct node {
 	bool deleted;
 	char *name;
 	struct property *proplist;
 	struct node *children;
+	int type;
+	unsigned int flags;
 
 	struct node *parent;
 	struct node *next_sibling;
@@ -297,4 +319,8 @@ struct boot_info *dt_from_source(const char *f);
 
 struct boot_info *dt_from_fs(const char *dirname);
 
+/* Schema source */
+
+struct boot_info *schema_from_source(const char *fname);
+
 #endif /* _DTC_H */
diff --git a/dtss-lexer.l b/dtss-lexer.l
new file mode 100644
index 0000000..aee41f6
--- /dev/null
+++ b/dtss-lexer.l
@@ -0,0 +1,291 @@
+/*
+ * (C) Copyright David Gibson <dwg at au1.ibm.com>, IBM Corporation.  2005.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+ *                                                                   USA
+ */
+
+%option noyywrap nounput noinput never-interactive prefix="dtss_yy"
+
+%x INCLUDE
+%x BYTESTRING
+%x PROPNODENAME
+%s V1
+
+PROPNODECHAR	[a-zA-Z0-9,._+*$#?^@-]
+PATHCHAR	({PROPNODECHAR}|[/])
+LABEL		[a-zA-Z_][a-zA-Z0-9_]*
+STRING		\"([^\\"]|\\.)*\"
+CHAR_LITERAL	'([^']|\\')*'
+WS		[[:space:]]
+COMMENT		"/*"([^*]|\*+[^*/])*\*+"/"
+LINECOMMENT	"//".*\n
+
+%{
+#include "dtc.h"
+#include "srcpos.h"
+
+#define DTSS_YYLTYPE struct srcpos
+
+#include "dtss-parser.tab.h"
+
+DTSS_YYLTYPE dtss_yylloc;
+extern bool treesource_error;
+
+/* CAUTION: this will stop working if we ever use dtss_yyless() or dtss_yyunput() */
+#define	YY_USER_ACTION \
+	{ \
+		srcpos_update(&dtss_yylloc, dtss_yytext, dtss_yyleng); \
+	}
+
+/* #define LEXDEBUG	1 */
+
+#ifdef LEXDEBUG
+#define DPRINT(fmt, ...)	fprintf(stderr, fmt, ##__VA_ARGS__)
+#else
+#define DPRINT(fmt, ...)	do { } while (0)
+#endif
+
+static int dts_version = 1;
+
+#define BEGIN_DEFAULT()		DPRINT("<V1>\n"); \
+				BEGIN(V1); \
+
+static void push_input_file(const char *filename);
+static bool pop_input_file(void);
+static void lexical_error(const char *fmt, ...);
+%}
+
+%%
+<*>"/include/"{WS}*{STRING} {
+			char *name = strchr(dtss_yytext, '\"') + 1;
+			dtss_yytext[dtss_yyleng-1] = '\0';
+			push_input_file(name);
+		}
+
+<*>^"#"(line)?[ \t]+[0-9]+[ \t]+{STRING}([ \t]+[0-9]+)? {
+			char *line, *tmp, *fn;
+			/* skip text before line # */
+			line = dtss_yytext;
+			while (!isdigit(*line))
+				line++;
+			/* skip digits in line # */
+			tmp = line;
+			while (!isspace(*tmp))
+				tmp++;
+			/* "NULL"-terminate line # */
+			*tmp = '\0';
+			/* start of filename */
+			fn = strchr(tmp + 1, '"') + 1;
+			/* strip trailing " from filename */
+			tmp = strchr(fn, '"');
+			*tmp = 0;
+			/* -1 since #line is the number of the next line */
+			srcpos_set_line(xstrdup(fn), atoi(line) - 1);
+		}
+
+<*><<EOF>>		{
+			if (!pop_input_file()) {
+				yyterminate();
+			}
+		}
+
+<*>{STRING}	{
+			DPRINT("String: %s\n", dtss_yytext);
+			dtss_yylval.data = data_copy_escape_string(dtss_yytext+1,
+					dtss_yyleng-2);
+			return DT_STRING;
+		}
+
+<*>"/dtss-v1/"	{
+			DPRINT("Keyword: /dtss-v1/\n");
+			dts_version = 0x8001;
+			BEGIN_DEFAULT();
+			return DTSS_V1;
+		}
+
+<*>"/bits/"	{
+			DPRINT("Keyword: /bits/\n");
+			BEGIN_DEFAULT();
+			return DT_BITS;
+		}
+
+<*>"/delete-property/"	{
+			DPRINT("Keyword: /delete-property/\n");
+			DPRINT("<PROPNODENAME>\n");
+			BEGIN(PROPNODENAME);
+			return DT_DEL_PROP;
+		}
+
+<*>"/delete-node/"	{
+			DPRINT("Keyword: /delete-node/\n");
+			DPRINT("<PROPNODENAME>\n");
+			BEGIN(PROPNODENAME);
+			return DT_DEL_NODE;
+		}
+
+<*>"/match/"	{
+			DPRINT("Keyword: /match/\n");
+			return DTSS_MATCH;
+		}
+
+<*>"/require/"	{
+			DPRINT("Keyword: /require/\n");
+			return DTSS_REQUIRE;
+		}
+
+<*>"/use/"	{
+			DPRINT("Keyword: /use/\n");
+			return DTSS_USE;
+		}
+
+<*>"/incomplete/"	{
+			DPRINT("Keyword: /incomplete/\n");
+			return DTSS_INCOMPLETE;
+		}
+
+<*>"/optional/"	{
+			DPRINT("Keyword: /optional/\n");
+			return DTSS_OPTIONAL;
+		}
+
+<V1>([0-9]+|0[xX][0-9a-fA-F]+)(U|L|UL|LL|ULL)? {
+			char *e;
+			DPRINT("Integer Literal: '%s'\n", yytext);
+
+			errno = 0;
+			dtss_yylval.integer = strtoull(yytext, &e, 0);
+
+			assert(!(*e) || !e[strspn(e, "UL")]);
+
+			if (errno == ERANGE)
+				lexical_error("Integer literal '%s' out of range",
+					      yytext);
+			else
+				/* ERANGE is the only strtoull error triggerable
+				 *  by strings matching the pattern */
+				assert(errno == 0);
+			return DT_LITERAL;
+		}
+
+<*>{CHAR_LITERAL}	{
+			struct data d;
+			DPRINT("Character literal: %s\n", yytext);
+
+			d = data_copy_escape_string(yytext+1, yyleng-2);
+			if (d.len == 1) {
+				lexical_error("Empty character literal");
+				dtss_yylval.integer = 0;
+				return DT_CHAR_LITERAL;
+			}
+
+			dtss_yylval.integer = (unsigned char)d.val[0];
+
+			if (d.len > 2)
+				lexical_error("Character literal has %d"
+					      " characters instead of 1",
+					      d.len - 1);
+
+			return DT_CHAR_LITERAL;
+		}
+
+<*>\&{LABEL}	{	/* label reference */
+			DPRINT("Ref: %s\n", dtss_yytext+1);
+			dtss_yylval.labelref = xstrdup(dtss_yytext+1);
+			return DT_REF;
+		}
+
+<*>"&{/"{PATHCHAR}*\}	{	/* new-style path reference */
+			dtss_yytext[dtss_yyleng-1] = '\0';
+			DPRINT("Ref: %s\n", dtss_yytext+2);
+			dtss_yylval.labelref = xstrdup(dtss_yytext+2);
+			return DT_REF;
+		}
+
+<BYTESTRING>[0-9a-fA-F]{2} {
+			dtss_yylval.byte = strtol(dtss_yytext, NULL, 16);
+			DPRINT("Byte: %02x\n", (int)dtss_yylval.byte);
+			return DT_BYTE;
+		}
+
+<BYTESTRING>"]"	{
+			DPRINT("/BYTESTRING\n");
+			BEGIN_DEFAULT();
+			return ']';
+		}
+
+<PROPNODENAME>\\?{PROPNODECHAR}+ {
+			DPRINT("PropNodeName: %s\n", dtss_yytext);
+			dtss_yylval.propnodename = xstrdup((dtss_yytext[0] == '\\') ?
+							dtss_yytext + 1 : dtss_yytext);
+			BEGIN_DEFAULT();
+			return DT_PROPNODENAME;
+		}
+
+<*>{WS}+	/* eat whitespace */
+<*>{COMMENT}+	/* eat C-style comments */
+<*>{LINECOMMENT}+ /* eat C++-style comments */
+
+<*>.		{
+			DPRINT("Char: %c (\\x%02x)\n", dtss_yytext[0],
+				(unsigned)dtss_yytext[0]);
+			if (dtss_yytext[0] == '[') {
+				DPRINT("<BYTESTRING>\n");
+				BEGIN(BYTESTRING);
+			}
+			if ((dtss_yytext[0] == '{')
+			    || (dtss_yytext[0] == ';')) {
+				DPRINT("<PROPNODENAME>\n");
+				BEGIN(PROPNODENAME);
+			}
+			return dtss_yytext[0];
+		}
+
+%%
+
+static void push_input_file(const char *filename)
+{
+	assert(filename);
+
+	srcfile_push(filename);
+
+	dtss_yyin = current_srcfile->f;
+
+	dtss_yypush_buffer_state(dtss_yy_create_buffer(dtss_yyin, YY_BUF_SIZE));
+}
+
+
+static bool pop_input_file(void)
+{
+	if (srcfile_pop() == 0)
+		return false;
+
+	dtss_yypop_buffer_state();
+	dtss_yyin = current_srcfile->f;
+
+	return true;
+}
+
+static void lexical_error(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	srcpos_verror(&dtss_yylloc, "Lexical error", fmt, ap);
+	va_end(ap);
+
+	treesource_error = true;
+}
diff --git a/dtss-parser.y b/dtss-parser.y
new file mode 100644
index 0000000..1c807da
--- /dev/null
+++ b/dtss-parser.y
@@ -0,0 +1,341 @@
+/*
+ * (C) Copyright David Gibson <dwg at au1.ibm.com>, IBM Corporation.  2005.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+ *                                                                   USA
+ */
+
+%define api.prefix dtss_yy
+
+%{
+#include <stdio.h>
+
+#include "dtc.h"
+#include "srcpos.h"
+
+#define DTSS_YYLTYPE struct srcpos
+
+extern int dtss_yylex(void);
+extern void dtss_yyerror(char const *s);
+#define ERROR(loc, ...) \
+	do { \
+		srcpos_error((loc), "Error", __VA_ARGS__); \
+		treesource_error = true; \
+	} while (0)
+
+extern struct boot_info *the_boot_info;
+extern bool treesource_error;
+%}
+
+%union {
+	char *propnodename;
+	char *labelref;
+	unsigned int cbase;
+	uint8_t byte;
+	struct data data;
+
+	struct {
+		struct data	data;
+		int		bits;
+	} array;
+
+	struct property *prop;
+	struct property *proplist;
+	struct node *node;
+	struct node *nodelist;
+	struct reserve_info *re;
+	uint64_t integer;
+}
+
+%token DTSS_V1
+%token DT_BITS
+%token DT_DEL_PROP
+%token DT_DEL_NODE
+%token DTSS_INCOMPLETE
+%token DTSS_MATCH
+%token DTSS_USE
+%token DTSS_REQUIRE
+%token DTSS_OPTIONAL
+%token <propnodename> DT_PROPNODENAME
+%token <integer> DT_LITERAL
+%token <integer> DT_CHAR_LITERAL
+%token <cbase> DT_BASE
+%token <byte> DT_BYTE
+%token <data> DT_STRING
+%token <data> DTSS_TYPESPEC
+%token <labelref> DT_LABEL
+%token <labelref> DT_REF
+%token DT_INCBIN
+
+%type <data> propdata
+%type <data> propdataprefix
+%type <array> arrayprefix
+%type <data> bytestring
+%type <prop> propdef
+%type <proplist> proplist
+
+%type <node> schema
+%type <node> schemaroot
+%type <node> nodedef
+%type <node> subnode
+%type <nodelist> subnodes
+
+%type <integer> integer_prim
+
+%%
+
+sourcefile:
+	  DTSS_V1 ';' schema
+		{
+			the_boot_info = build_boot_info(NULL, $3, 0);
+		}
+	;
+
+schemaroot:
+	subnodes
+		{
+			$$ = build_node(NULL, $1);
+		}
+	;
+
+schema:
+	schemaroot
+		{
+			$$ = name_node($1, "/");
+		}
+	;
+
+nodedef:
+	  '{' proplist subnodes '}' ';'
+		{
+			$$ = build_node($2, $3);
+		}
+	;
+
+proplist:
+	  /* empty */
+		{
+			$$ = NULL;
+		}
+	| proplist propdef
+		{
+			$$ = chain_property($2, $1);
+		}
+	;
+
+propdef:
+	  DT_PROPNODENAME '=' propdata ';'
+		{
+			$$ = build_property($1, $3);
+		}
+	| DT_PROPNODENAME ';'
+		{
+			$$ = build_property($1, empty_data);
+		}
+	| DT_DEL_PROP DT_PROPNODENAME ';'
+		{
+			$$ = build_property_delete($2);
+		}
+	| DTSS_MATCH propdef
+		{
+			$2->type = PROPERTY_MATCH;
+			$$ = $2;
+		}
+	| DTSS_USE propdef
+		{
+			$2->type = PROPERTY_USE;
+			$$ = $2;
+		}
+	| DTSS_REQUIRE propdef
+		{
+			$2->type = PROPERTY_REQUIRE;
+			$$ = $2;
+		}
+	| DTSS_OPTIONAL propdef
+		{
+			$2->flags |= PROPERTY_FLAG_OPTIONAL;
+			$$ = $2;
+		}
+	;
+
+propdata:
+	  propdataprefix DT_STRING
+		{
+			$$ = data_merge($1, $2);
+		}
+	| propdataprefix arrayprefix '>'
+		{
+			$$ = data_merge($1, $2.data);
+		}
+	| propdataprefix '[' bytestring ']'
+		{
+			$$ = data_merge($1, $3);
+		}
+	| propdataprefix DT_INCBIN '(' DT_STRING ',' integer_prim ',' integer_prim ')'
+		{
+			FILE *f = srcfile_relative_open($4.val, NULL);
+			struct data d;
+
+			if ($6 != 0)
+				if (fseek(f, $6, SEEK_SET) != 0)
+					die("Couldn't seek to offset %llu in \"%s\": %s",
+					    (unsigned long long)$6, $4.val,
+					    strerror(errno));
+
+			d = data_copy_file(f, $8);
+
+			$$ = data_merge($1, d);
+			fclose(f);
+		}
+	| propdataprefix DT_INCBIN '(' DT_STRING ')'
+		{
+			FILE *f = srcfile_relative_open($4.val, NULL);
+			struct data d = empty_data;
+
+			d = data_copy_file(f, -1);
+
+			$$ = data_merge($1, d);
+			fclose(f);
+		}
+	;
+
+propdataprefix:
+	  /* empty */
+		{
+			$$ = empty_data;
+		}
+	| propdata ','
+		{
+			$$ = $1;
+		}
+	| propdataprefix DT_LABEL
+		{
+			$$ = data_add_marker($1, LABEL, $2);
+		}
+	;
+
+arrayprefix:
+	DT_BITS DT_LITERAL '<'
+		{
+			unsigned long long bits;
+
+			bits = $2;
+
+			if ((bits !=  8) && (bits != 16) &&
+			    (bits != 32) && (bits != 64)) {
+				ERROR(&@2, "Array elements must be"
+				      " 8, 16, 32 or 64-bits");
+				bits = 32;
+			}
+
+			$$.data = empty_data;
+			$$.bits = bits;
+		}
+	| '<'
+		{
+			$$.data = empty_data;
+			$$.bits = 32;
+		}
+	| arrayprefix integer_prim
+		{
+			if ($1.bits < 64) {
+				uint64_t mask = (1ULL << $1.bits) - 1;
+				/*
+				 * Bits above mask must either be all zero
+				 * (positive within range of mask) or all one
+				 * (negative and sign-extended). The second
+				 * condition is true if when we set all bits
+				 * within the mask to one (i.e. | in the
+				 * mask), all bits are one.
+				 */
+				if (($2 > mask) && (($2 | mask) != -1ULL))
+					ERROR(&@2, "Value out of range for"
+					      " %d-bit array element", $1.bits);
+			}
+
+			$$.data = data_append_integer($1.data, $2, $1.bits);
+		}
+	;
+
+integer_prim:
+	  DT_LITERAL
+	| DT_CHAR_LITERAL
+	;
+
+bytestring:
+	  /* empty */
+		{
+			$$ = empty_data;
+		}
+	| bytestring DT_BYTE
+		{
+			$$ = data_append_byte($1, $2);
+		}
+	;
+
+subnodes:
+	  /* empty */
+		{
+			$$ = NULL;
+		}
+	| subnode subnodes
+		{
+			$$ = chain_node($1, $2);
+		}
+	| subnode propdef
+		{
+			ERROR(&@2, "Properties must precede subnodes");
+			YYERROR;
+		}
+	;
+
+subnode:
+	  DT_PROPNODENAME nodedef
+		{
+			$$ = name_node($2, $1);
+		}
+	| DT_DEL_NODE DT_PROPNODENAME ';'
+		{
+			$$ = name_node(build_node_delete(), $2);
+		}
+	| DTSS_USE subnode
+		{
+			$2->type = NODE_USE;
+			$$ = $2;
+		}
+	| DTSS_REQUIRE subnode
+		{
+			$2->type = NODE_REQUIRE;
+			$$ = $2;
+		}
+	| DTSS_OPTIONAL subnode
+		{
+			$2->flags |= NODE_FLAG_OPTIONAL;
+			$$ = $2;
+		}
+	| DTSS_INCOMPLETE subnode
+		{
+			$2->flags |= NODE_FLAG_INCOMPLETE;
+			$$ = $2;
+		}
+	;
+
+%%
+
+void dtss_yyerror(char const *s)
+{
+	ERROR(&yylloc, "%s", s);
+}
diff --git a/schemas/schema.c b/schemas/schema.c
new file mode 100644
index 0000000..e5258cf
--- /dev/null
+++ b/schemas/schema.c
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2013 Stephen Warren <swarren at wwwdotorg.org>
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd.
+ *	Tomasz Figa <t.figa at samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+ *                                                                   USA
+ */
+
+#include "schema.h"
+
+static struct schema_checker schema_list = {
+	.next = &schema_list,
+};
+
+int schema_check_node(struct node *root, struct node *node)
+{
+	const struct schema_checker *checker;
+	int match;
+	int checked = 0;
+
+	checker = schema_list.next;
+	for (; checker != &schema_list; checker = checker->next) {
+		match = checker->matchfn(node, checker);
+		if (!match)
+			continue;
+
+		pr_info("Node %s matches checker %s at level %d\n",
+			node->fullpath, checker->name, match);
+
+		checker->checkfn(root, node, checker);
+		checked = 1;
+	}
+
+	/*
+	 * FIXME: this is too noisy right now. Make it optional until schemas
+	 * for most bindings are implemented.
+	 */
+	if (!checked) {
+		pr_warn("no schema for node %s\n", node->fullpath);
+		return 0;
+	}
+
+	/*
+	 * FIXME: grab validation state from global somewhere.
+	 * Using global state avoids having check return values after every
+	 * function call, thus making the code less verbose and appear more
+	 * assertion-based.
+	 */
+	return 0;
+}
+
+int schema_match_path(struct node *node, const struct schema_checker *checker)
+{
+	return !strcmp(node->fullpath, checker->u.path.path);
+}
+
+int schema_match_compatible(struct node *node,
+				const struct schema_checker *checker)
+{
+	struct property *compat_prop;
+	int index;
+	const char *node_compat;
+	const char **test_compats;
+
+	compat_prop = get_property(node, "compatible");
+	if (!compat_prop)
+		return 0;
+
+	/*
+	 * Match with any compatible value of schema with any compatible
+	 * value of node being verified.
+	 */
+	for (node_compat = compat_prop->val.val, index = 0;
+			*node_compat;
+			node_compat += strlen(node_compat) + 1, index++) {
+		for (test_compats = checker->u.compatible.compats;
+				*test_compats; test_compats++) {
+			if (!strcmp(node_compat, *test_compats))
+				return 1;
+		}
+	}
+
+	return 0;
+}
+
+struct property *schema_get_param(struct node *params, const char *name)
+{
+	if (!params)
+		return NULL;
+
+	return get_property(params, name);
+}
+
+int schema_get_param_cell(struct node *params, const char *name, cell_t *val)
+{
+	struct property *prop;
+
+	prop = schema_get_param(params, name);
+	if (!prop)
+		return -ENOENT;
+
+	if (!prop->val.len)
+		return -EINVAL;
+
+	*val = propval_cell(prop);
+	return 0;
+}
+
+struct property *require_property(struct node *node, const char *name)
+{
+	struct property *prop;
+
+	prop = get_property(node, name);
+	if (!prop) {
+		/*
+		 * FIXME: set global error state. The same comment applies
+		 * everywhere.
+		 */
+		pr_err("node '%s' missing '%s' property\n",
+			node->fullpath, name);
+	}
+
+	return prop;
+}
+
+static void check_required_property(struct node *node, struct property *schema)
+{
+	struct property *prop;
+
+	prop = require_property(node, schema->name);
+	if (!prop)
+		return;
+
+	if (schema->val.len
+	    && (schema->val.len != prop->val.len
+	    || memcmp(schema->val.val, prop->val.val, prop->val.len)))
+		pr_err("node %s with wrong constant value of property %s\n",
+			node->fullpath, schema->name);
+}
+
+static void check_optional_property(struct node *node, struct property *schema)
+{
+	struct property *prop;
+
+	prop = get_property(node, schema->name);
+	if (!prop)
+		return;
+
+	check_required_property(node, schema);
+}
+
+/*
+ * FIXME: Use a more generic solution, which does not rely on linker
+ * specific features.
+ */
+extern const struct generic_schema __start_generic_schemas;
+extern const struct generic_schema __stop_generic_schemas;
+
+static void check_generic_schema(struct node *root, struct node *node,
+					const char *name,
+					struct node *schema_node,
+					bool required)
+{
+	const struct generic_schema *gs;
+	int i;
+	bool checked = false;
+	unsigned int count = &__stop_generic_schemas - &__start_generic_schemas;
+
+	pr_info("running schema \"%s\"\n", name);
+
+	gs = &__start_generic_schemas;
+	for (i = 0; i < count; ++i, ++gs) {
+		if (strcmp(gs->name, name))
+			continue;
+
+		gs->checkfn(gs, root, node, schema_node, required);
+
+		checked = true;
+	}
+
+	if (!checked)
+		pr_err("schema \"%s\" not found\n", name);
+}
+
+static void check_dtss_schema(struct node *root, struct node *node,
+			      const struct schema_checker *checker)
+{
+	struct property *prop_schema;
+	struct node *schema = checker->node, *node_schema;
+
+	for_each_property(schema, prop_schema) {
+		if (!strcmp(prop_schema->name, "compatible")
+		    || !strcmp(prop_schema->name, "device_type"))
+			continue;
+
+		switch (prop_schema->type) {
+		case PROPERTY_DATA:
+			if (prop_schema->flags & PROPERTY_FLAG_OPTIONAL)
+				check_optional_property(node, prop_schema);
+			else
+				check_required_property(node, prop_schema);
+			break;
+
+		case PROPERTY_REQUIRE:
+			check_generic_schema(root, node, prop_schema->name,
+						NULL, true);
+			break;
+
+		case PROPERTY_USE:
+			check_generic_schema(root, node, prop_schema->name,
+						NULL, false);
+			break;
+		}
+	}
+
+	for_each_child(schema, node_schema) {
+		switch (node_schema->type) {
+		case NODE_DATA:
+			/* TODO: verify subnodes */
+			break;
+
+		case NODE_REQUIRE:
+			check_generic_schema(root, node, node_schema->name,
+						node_schema, true);
+			break;
+
+		case NODE_USE:
+			check_generic_schema(root, node, node_schema->name,
+						node_schema, false);
+			break;
+		}
+	}
+
+	/* TODO: detect unknown properties */
+}
+
+void build_schema_list(struct boot_info *schema_tree)
+{
+	struct node *root, *schema;
+
+	root = get_node_by_path(schema_tree->dt, "/");
+	if (!root) {
+		pr_err("schema file missing / node\n");
+		return;
+	}
+
+	for_each_child(root, schema) {
+		struct schema_checker *sc = xmalloc(sizeof(*sc));
+		struct property *prop;
+
+		sc->node = schema;
+		sc->checkfn = check_dtss_schema;
+		sc->name = schema->name;
+
+		for_each_property(schema, prop)
+			if (prop->type == PROPERTY_MATCH)
+				goto found_match;
+
+		pr_err("schema '%s' without matching key\n", sc->name);
+		free(sc);
+		continue;
+
+found_match:
+		if (!strcmp(prop->name, "compatible")) {
+			int count;
+			const char **compats;
+			const char *compat;
+
+			count = propval_string_count(schema, prop) + 1;
+			compats = xmalloc(count * sizeof(*compats));
+
+			sc->u.compatible.compats = compats;
+
+			while ((compat = propval_next_string(prop, compat))) {
+				*compats = compat;
+				++compats;
+			}
+			*compats = NULL;
+
+			sc->matchfn = schema_match_compatible;
+			sc->next = schema_list.next;
+			schema_list.next = sc;
+		} else if (!strcmp(prop->name, "device_type")) {
+			sc->u.type.type = propval_next_string(prop, NULL);
+			sc->next = schema_list.next;
+			schema_list.next = sc;
+		} else if (!strcmp(prop->name, "path")) {
+			sc->u.path.path = propval_next_string(prop, NULL);
+			sc->matchfn = schema_match_path;
+			sc->next = schema_list.next;
+			schema_list.next = sc;
+		} else {
+			pr_err("wrong schema key type\n");
+			free(sc);
+		}
+	}
+}
diff --git a/schemas/schema.h b/schemas/schema.h
new file mode 100644
index 0000000..9972a17
--- /dev/null
+++ b/schemas/schema.h
@@ -0,0 +1,89 @@
+#ifndef _SCHEMAS_SCHEMA_H
+#define _SCHEMAS_SCHEMA_H
+
+#include "dtc.h"
+
+struct schema_checker;
+
+typedef int (schema_matcher_func)(struct node *node,
+					const struct schema_checker *checker);
+typedef void (schema_checker_func)(struct node *root, struct node *node,
+					const struct schema_checker *checker);
+
+struct schema_checker {
+	const char *name;
+	schema_matcher_func *matchfn;
+	schema_checker_func *checkfn;
+	struct node *node;
+	union {
+		struct {
+			const char *path;
+		} path;
+		struct {
+			const char **compats;
+		} compatible;
+		struct {
+			const char *type;
+		} type;
+	} u;
+	struct schema_checker *next;
+};
+
+int schema_check_node(struct node *root, struct node *node);
+
+int schema_match_path(struct node *node, const struct schema_checker *checker);
+int schema_match_compatible(struct node *node,
+				const struct schema_checker *checker);
+
+#define SCHEMA_MATCH_PATH(_name_, _path_) \
+	struct schema_checker schema_checker_##_name_ = { \
+		.name = #_name_, \
+		.matchfn = schema_match_path, \
+		.checkfn = checkfn_##_name_, \
+		.u.path.path = _path_, \
+	};
+
+#define SCHEMA_MATCH_COMPATIBLE(_name_) \
+	struct schema_checker schema_checker_##_name_ = { \
+		.name = #_name_, \
+		.matchfn = schema_match_compatible, \
+		.checkfn = checkfn_##_name_, \
+		.u.compatible.compats = compats_##_name_, \
+	};
+
+struct generic_schema;
+
+typedef void (generic_schema_checker_func)(const struct generic_schema *schema,
+					   struct node *root, struct node *node,
+					   struct node *params, bool required);
+
+struct generic_schema {
+	const char *name;
+	generic_schema_checker_func *checkfn;
+};
+
+#define __used		__attribute__((__used__))
+#define __section(S)	__attribute__ ((__section__(#S)))
+
+#define GENERIC_SCHEMA(_dt_name_, _name_)				\
+	static const struct generic_schema generic_schema_##_name_	\
+		__used __section(generic_schemas) = {			\
+			.name = _dt_name_,				\
+			.checkfn = generic_checkfn_##_name_,		\
+	};
+
+struct property *require_property(struct node *node, const char *propname);
+struct property *schema_get_param(struct node *params, const char *name);
+int schema_get_param_cell(struct node *params, const char *name, cell_t *val);
+
+void build_schema_list(struct boot_info *schema_tree);
+
+#define schema_err(s,fmt,args...)	pr_err("%s: " fmt, (s)->name, ##args)
+#define schema_warn(s,fmt,args...)	pr_warn("%s: " fmt, (s)->name, ##args)
+#define schema_info(s,fmt,args...)	pr_info("%s: " fmt, (s)->name, ##args)
+
+#define node_err(n,fmt,args...)		pr_err("%s: " fmt, (n)->fullpath, ##args)
+#define node_warn(n,fmt,args...)	pr_warn("%s: " fmt, (n)->fullpath, ##args)
+#define node_info(n,fmt,args...)	pr_info("%s: " fmt, (n)->fullpath, ##args)
+
+#endif
diff --git a/srcpos.h b/srcpos.h
index f81827b..b761522 100644
--- a/srcpos.h
+++ b/srcpos.h
@@ -75,7 +75,9 @@ struct srcpos {
     struct srcfile_state *file;
 };
 
+#ifndef YYLTYPE
 #define YYLTYPE struct srcpos
+#endif
 
 #define YYLLOC_DEFAULT(Current, Rhs, N)						\
 	do {									\
diff --git a/treesource.c b/treesource.c
index bf7a626..e50285c 100644
--- a/treesource.c
+++ b/treesource.c
@@ -25,6 +25,10 @@ extern FILE *yyin;
 extern int yyparse(void);
 extern YYLTYPE yylloc;
 
+extern FILE *dtss_yyin;
+extern int dtss_yyparse(void);
+extern YYLTYPE dtss_yylloc;
+
 struct boot_info *the_boot_info;
 bool treesource_error;
 
@@ -46,6 +50,24 @@ struct boot_info *dt_from_source(const char *fname)
 	return the_boot_info;
 }
 
+struct boot_info *schema_from_source(const char *fname)
+{
+	the_boot_info = NULL;
+	treesource_error = false;
+
+	srcfile_push(fname);
+	dtss_yyin = current_srcfile->f;
+	dtss_yylloc.file = current_srcfile;
+
+	if (dtss_yyparse() != 0)
+		die("Unable to parse input tree\n");
+
+	if (treesource_error)
+		die("Syntax error parsing input tree\n");
+
+	return the_boot_info;
+}
+
 static void write_prefix(FILE *f, int level)
 {
 	int i;
-- 
1.8.5.2




More information about the linux-arm-kernel mailing list