All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing
@ 2026-11-01 16:36 Simon Glass
  2026-11-01 16:36 ` [PATCH 01/13] tests: Check each error when merging a dts file Simon Glass
                   ` (12 more replies)
  0 siblings, 13 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The parser merges each top-level fragment of a dts file into the tree as
soon as it is parsed. This means that deletes cannot be dealt with
consistently, nor checks run on each fragment before it is merged. Here
a fragment is a top-level part of the source, such as / { ... }; or
&label { ... };, not an overlay's fragment@N node.

As a first step towards keeping the fragments apart until after parsing,
move the merge logic out of the grammar actions into livetree.c, one
action at a time, then have the parser return a list of fragments and
merge them from main(). The output is unchanged. The only changes to the
errors are the final message when a reference cannot be resolved, that a
syntax error later in the file now stops dtc before any earlier
reference errors are reported, and that reference errors now come after
any other errors from the parser.

Most of the merge errors have no tests, so the series starts by adding a
test for each one, checking the message and its position in the source.
Every line of the new merge code is then covered by the tests. At each
commit, make check passes and dtc gives the same output and messages as
before for all 4162 .dts and .dtso files in Linux
v7.3-12284-g3799eeb906c9

Following this, the plan is to apply deletes while merging, deciding
what a delete in a plugin should do, and then to run some checks on each
fragment.

Link: https://lore.kernel.org/devicetree-compiler/auK0ERvxGCJGSc3p@gractus.seuss/


Simon Glass (13):
  tests: Check each error when merging a dts file
  dtc: Add a list for the top-level fragments of a dts file
  dtc: Move the /delete-node/ handling out of the grammar
  dtc: Move the /omit-if-no-ref/ handling out of the grammar
  dtc: Move is_ref_relative() to livetree.c
  dtc: Move label-reference handling out of the grammar
  dtc: Move path-reference handling out of the grammar
  dtc: Move labelled-reference handling out of the grammar
  dtc: Move first-reference handling out of the grammar
  dtc: Add merge_fragment() to merge any fragment
  dtc: Add helpers for the errors when merging a fragment
  dtc: Merge the fragments of a dts file after parsing
  dtc: Merge the fragments of a dts file in main()

 dtc-parser.y                       | 130 ++++---------
 dtc.c                              |   2 +
 dtc.h                              |  48 +++++
 livetree.c                         | 302 ++++++++++++++++++++++++++++-
 tests/merge-delete-missing.dts     |   9 +
 tests/merge-errors.dts             |  14 ++
 tests/merge-first-ref-relative.dts |  10 +
 tests/merge-first-ref.dts          |   9 +
 tests/merge-labelled-plugin.dts    |  12 ++
 tests/merge-labelled-relative.dts  |  14 ++
 tests/merge-omit-missing.dts       |   9 +
 tests/merge-parse-error.dts        |  17 ++
 tests/merge-path-ref-missing.dts   |  11 ++
 tests/merge-path-relative.dts      |  12 ++
 tests/run_tests.sh                 |  44 ++++-
 treesource.c                       |   5 +-
 16 files changed, 551 insertions(+), 97 deletions(-)
 create mode 100644 tests/merge-delete-missing.dts
 create mode 100644 tests/merge-errors.dts
 create mode 100644 tests/merge-first-ref-relative.dts
 create mode 100644 tests/merge-first-ref.dts
 create mode 100644 tests/merge-labelled-plugin.dts
 create mode 100644 tests/merge-labelled-relative.dts
 create mode 100644 tests/merge-omit-missing.dts
 create mode 100644 tests/merge-parse-error.dts
 create mode 100644 tests/merge-path-ref-missing.dts
 create mode 100644 tests/merge-path-relative.dts

---
base-commit: c22868416b8db8354ce5db29d3494dda688a2ae3
branch: dt-forest

-- 
2.43.0


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

* [PATCH 01/13] tests: Check each error when merging a dts file
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
@ 2026-11-01 16:36 ` Simon Glass
  2026-11-02  6:32   ` David Gibson
  2026-11-01 16:36 ` [PATCH 02/13] dtc: Add a list for the top-level fragments of " Simon Glass
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

When the parser merges the top-level fragments of a dts file, it can
report that a label or path is not found, or that a label-relative
reference is not supported in a plugin. Most of these errors are not
covered by any test, and the two tests which do reach them only check
that dtc fails, not the message or where it is reported. The merge
logic is about to move out of the grammar, so this needs better tests.

Add a test file for each error which is not covered, and check the
message and its position for all of them, including the two existing
ones. Also check that merging carries on after an error, so that later
errors are reported too, that a reference error is still reported when
the parser finds another error, and that a labelled reference within a
plugin adds the label, since nothing covers these either.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 tests/merge-delete-missing.dts     |  9 +++++++
 tests/merge-errors.dts             | 14 ++++++++++
 tests/merge-first-ref-relative.dts | 10 ++++++++
 tests/merge-first-ref.dts          |  9 +++++++
 tests/merge-labelled-plugin.dts    | 12 +++++++++
 tests/merge-labelled-relative.dts  | 14 ++++++++++
 tests/merge-omit-missing.dts       |  9 +++++++
 tests/merge-parse-error.dts        | 17 +++++++++++++
 tests/merge-path-ref-missing.dts   | 11 ++++++++
 tests/merge-path-relative.dts      | 12 +++++++++
 tests/run_tests.sh                 | 41 ++++++++++++++++++++++++++++++
 11 files changed, 158 insertions(+)
 create mode 100644 tests/merge-delete-missing.dts
 create mode 100644 tests/merge-errors.dts
 create mode 100644 tests/merge-first-ref-relative.dts
 create mode 100644 tests/merge-first-ref.dts
 create mode 100644 tests/merge-labelled-plugin.dts
 create mode 100644 tests/merge-labelled-relative.dts
 create mode 100644 tests/merge-omit-missing.dts
 create mode 100644 tests/merge-parse-error.dts
 create mode 100644 tests/merge-path-ref-missing.dts
 create mode 100644 tests/merge-path-relative.dts

diff --git a/tests/merge-delete-missing.dts b/tests/merge-delete-missing.dts
new file mode 100644
index 0000000..b36e39e
--- /dev/null
+++ b/tests/merge-delete-missing.dts
@@ -0,0 +1,9 @@
+/dts-v1/;
+
+/ {
+	label: node {
+	};
+};
+
+/* Try to delete a node using a non-existent label */
+/delete-node/ &nosuchnode;
diff --git a/tests/merge-errors.dts b/tests/merge-errors.dts
new file mode 100644
index 0000000..6d3969f
--- /dev/null
+++ b/tests/merge-errors.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+
+/ {
+	label: node {
+	};
+};
+
+/* Each error is reported, not just the first */
+&nosuchnode1 {
+	prop;
+};
+
+/delete-node/ &nosuchnode2;
+/omit-if-no-ref/ &nosuchnode3;
diff --git a/tests/merge-first-ref-relative.dts b/tests/merge-first-ref-relative.dts
new file mode 100644
index 0000000..103cd1f
--- /dev/null
+++ b/tests/merge-first-ref-relative.dts
@@ -0,0 +1,10 @@
+/dts-v1/;
+/plugin/;
+
+/* A plugin cannot start with a label-relative reference */
+&{label/node} {
+	prop;
+};
+
+/* Later fragments are still merged */
+/delete-node/ &nosuchnode;
diff --git a/tests/merge-first-ref.dts b/tests/merge-first-ref.dts
new file mode 100644
index 0000000..8f23a44
--- /dev/null
+++ b/tests/merge-first-ref.dts
@@ -0,0 +1,9 @@
+/dts-v1/;
+
+/* Only a plugin can start with a reference */
+&nosuchnode {
+	prop;
+};
+
+/* Later fragments are still merged */
+/delete-node/ &nosuchnode2;
diff --git a/tests/merge-labelled-plugin.dts b/tests/merge-labelled-plugin.dts
new file mode 100644
index 0000000..3849cc5
--- /dev/null
+++ b/tests/merge-labelled-plugin.dts
@@ -0,0 +1,12 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	label: node {
+	};
+};
+
+/* Add a label to a node defined in the plugin itself */
+newlabel: &label {
+	prop = "value";
+};
diff --git a/tests/merge-labelled-relative.dts b/tests/merge-labelled-relative.dts
new file mode 100644
index 0000000..00369e3
--- /dev/null
+++ b/tests/merge-labelled-relative.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	label: node {
+		subnode {
+		};
+	};
+};
+
+/* A plugin cannot use a label-relative reference, even if it is found */
+newlabel: &{label/subnode} {
+	prop;
+};
diff --git a/tests/merge-omit-missing.dts b/tests/merge-omit-missing.dts
new file mode 100644
index 0000000..0357648
--- /dev/null
+++ b/tests/merge-omit-missing.dts
@@ -0,0 +1,9 @@
+/dts-v1/;
+
+/ {
+	label: node {
+	};
+};
+
+/* Try to mark a node using a non-existent path */
+/omit-if-no-ref/ &{/nosuchnode};
diff --git a/tests/merge-parse-error.dts b/tests/merge-parse-error.dts
new file mode 100644
index 0000000..c39318b
--- /dev/null
+++ b/tests/merge-parse-error.dts
@@ -0,0 +1,17 @@
+/dts-v1/;
+
+/ {
+	label: node {
+	};
+};
+
+/* Reported even though the parser finds another error below */
+&nosuchnode {
+	prop;
+};
+
+/ {
+	node {
+		prop = <(1/0)>;
+	};
+};
diff --git a/tests/merge-path-ref-missing.dts b/tests/merge-path-ref-missing.dts
new file mode 100644
index 0000000..ad2e35e
--- /dev/null
+++ b/tests/merge-path-ref-missing.dts
@@ -0,0 +1,11 @@
+/dts-v1/;
+
+/ {
+	node {
+	};
+};
+
+/* Try to redefine a node using a non-existent path */
+&{/nosuchnode} {
+	prop;
+};
diff --git a/tests/merge-path-relative.dts b/tests/merge-path-relative.dts
new file mode 100644
index 0000000..94a0b38
--- /dev/null
+++ b/tests/merge-path-relative.dts
@@ -0,0 +1,12 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	label: node {
+	};
+};
+
+/* A plugin cannot use a label-relative path reference */
+&{label/node} {
+	prop;
+};
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index 5bc7817..e0504c0 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -765,6 +765,47 @@ dtc_tests () {
     run_sh_test "$SRCDIR/dtc-checkfails.sh" phandle_references -- -I dts -O dtb "$SRCDIR/nonexist-node-ref.dts"
     run_sh_test "$SRCDIR/dtc-checkfails.sh" phandle_references -- -I dts -O dtb "$SRCDIR/nonexist-label-ref.dts"
     run_sh_test "$SRCDIR/dtc-fatal.sh" -I dts -O dtb "$SRCDIR/nonexist-node-ref2.dts"
+
+    # Check each error when merging the fragments of a dts file, with its
+    # position
+    run_wrap_error_msg_test "test_label_ref.dts:7.8-15 Label or path handle not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/test_label_ref.dts"
+    run_wrap_error_msg_test "nonexist-node-ref2.dts:9.1-12 Label or path nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/nonexist-node-ref2.dts"
+    run_wrap_error_msg_test "merge-first-ref.dts:4.13-6.3 Label or path nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref.dts"
+    run_wrap_error_msg_test "merge-first-ref.dts:9.15-27 Label or path nosuchnode2 not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref.dts"
+    run_wrap_error_msg_test "merge-first-ref-relative.dts:5.15-7.3 Label-relative reference label/node not supported in plugin" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref-relative.dts"
+    run_wrap_error_msg_test "merge-first-ref-relative.dts:10.15-26 Label or path nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref-relative.dts"
+    run_wrap_error_msg_test "merge-labelled-relative.dts:12.1-10 Label-relative reference label/subnode not supported in plugin" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-labelled-relative.dts"
+    run_wrap_error_msg_test "merge-path-relative.dts:10.1-14 Label-relative reference label/node not supported in plugin" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-path-relative.dts"
+    run_wrap_error_msg_test "merge-path-ref-missing.dts:9.1-15 Label or path /nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-path-ref-missing.dts"
+    run_wrap_error_msg_test "merge-delete-missing.dts:9.15-26 Label or path nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-delete-missing.dts"
+    run_wrap_error_msg_test "merge-omit-missing.dts:9.18-32 Label or path /nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-omit-missing.dts"
+    run_wrap_error_msg_test "merge-errors.dts:9.1-13 Label or path nosuchnode1 not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
+    run_wrap_error_msg_test "merge-errors.dts:13.15-27 Label or path nosuchnode2 not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
+    run_wrap_error_msg_test "merge-errors.dts:14.18-30 Label or path nosuchnode3 not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
+    run_wrap_error_msg_test "merge-parse-error.dts:9.1-12 Label or path nosuchnode not found" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-parse-error.dts"
+    run_wrap_error_msg_test "merge-parse-error.dts:15.12-15 Division by zero" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-parse-error.dts"
+
+    # A labelled reference to a node in a plugin adds the label to it
+    run_dtc_test -@ -I dts -O dtb -o merge-labelled-plugin.test.dtb \
+	"$SRCDIR/merge-labelled-plugin.dts"
+    run_fdtget_test "/node" merge-labelled-plugin.test.dtb /__symbols__ newlabel
+    run_fdtget_test "value" merge-labelled-plugin.test.dtb /node prop
     check_tests "$SRCDIR/bad-name-property.dts" name_properties
 
     check_tests "$SRCDIR/bad-ncells.dts" address_cells_is_cell size_cells_is_cell interrupts_extended_is_cell
-- 
2.43.0


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

* [PATCH 02/13] dtc: Add a list for the top-level fragments of a dts file
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
  2026-11-01 16:36 ` [PATCH 01/13] tests: Check each error when merging a dts file Simon Glass
@ 2026-11-01 16:36 ` Simon Glass
  2026-11-02  9:49   ` David Gibson
  2026-11-01 16:36 ` [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar Simon Glass
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

A dts file is made up of top-level fragments: the root node, later
definitions of a node by label or path, and /delete-node/ or
/omit-if-no-ref/ with a reference. We would like to deal with each
fragment on its own before it is merged, for example to apply deletes
consistently or to run some checks on each one, but that requires
keeping the fragments apart. At present the parser merges each one into
the tree as soon as it is parsed.

Add a struct fragment to hold one of these, functions to build one, to
append it to a list and to free it, and a list of them in struct dt_info

These are not used yet.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc.h      | 47 ++++++++++++++++++++++++++++++++++++++++++
 livetree.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 107 insertions(+)

diff --git a/dtc.h b/dtc.h
index 473552e..c0d8e8f 100644
--- a/dtc.h
+++ b/dtc.h
@@ -322,12 +322,59 @@ struct reserve_info *chain_reserve_entry(struct reserve_info *first,
 struct reserve_info *add_reserve_entry(struct reserve_info *list,
 				       struct reserve_info *new);
 
+/*
+ * A fragment is one top-level part of a dts file, after the header and any
+ * memory reservations: the root node, a later definition of a node which
+ * refers to it by label or path, or a /delete-node/ or /omit-if-no-ref/ with
+ * a reference. Fragments are kept apart until they are merged into a single
+ * tree.
+ *
+ * This is not the same as an overlay fragment (a fragment@N node), although
+ * in a plugin a fragment may become one when it is merged.
+ */
+
+enum fragment_type {
+	FRAGMENT_ROOT,		/* / { ... }; */
+	FRAGMENT_REF,		/* [label:] &ref { ... }; */
+	FRAGMENT_DELETE_NODE,	/* /delete-node/ &ref; */
+	FRAGMENT_OMIT_IF_NO_REF, /* /omit-if-no-ref/ &ref; */
+};
+
+/*
+ * Which fields are used depends on the type:
+ *
+ *   FRAGMENT_ROOT            node
+ *   FRAGMENT_REF             ref, path_ref, ref_srcpos and node, plus label
+ *                            and label_srcpos if there is a label
+ *   FRAGMENT_DELETE_NODE     ref, path_ref and ref_srcpos
+ *   FRAGMENT_OMIT_IF_NO_REF  ref, path_ref and ref_srcpos
+ *
+ * Unused fields are NULL or false.
+ */
+struct fragment {
+	enum fragment_type type;
+	char *ref;			/* label or path referred to */
+	bool path_ref;			/* ref is a path, i.e. &{...} */
+	struct srcpos *ref_srcpos;	/* position of ref */
+	char *label;			/* label to add to the target */
+	struct srcpos *label_srcpos;	/* position of label */
+	struct node *node;		/* the node definition */
+
+	struct fragment *next;		/* NULL at the end of the list */
+};
+
+struct fragment *build_fragment(enum fragment_type type, struct node *node);
+struct fragment *build_fragment_ref(char *ref, bool path_ref,
+				    struct srcpos *srcpos);
+struct fragment *append_fragment(struct fragment *list, struct fragment *new);
+void free_fragment(struct fragment *f);
 
 struct dt_info {
 	unsigned int dtsflags;
 	struct reserve_info *reservelist;
 	uint32_t boot_cpuid_phys;
 	struct node *dt;		/* the device tree */
+	struct fragment *fragments;	/* not yet merged into dt */
 	const char *outname;		/* filename being written to, "-" for stdout */
 };
 
diff --git a/livetree.c b/livetree.c
index abe73de..982f0a6 100644
--- a/livetree.c
+++ b/livetree.c
@@ -448,6 +448,65 @@ struct reserve_info *add_reserve_entry(struct reserve_info *list,
 	return list;
 }
 
+/* Create a fragment of the given type, holding an optional node */
+struct fragment *build_fragment(enum fragment_type type, struct node *node)
+{
+	struct fragment *new = xmalloc(sizeof(*new));
+
+	memset(new, '\0', sizeof(*new));
+
+	new->type = type;
+	new->node = node;
+
+	return new;
+}
+
+/*
+ * Create a fragment which refers to a label, or to a path if path_ref. Its
+ * node is NULL, so the caller must either set the node or change the type to
+ * FRAGMENT_DELETE_NODE or FRAGMENT_OMIT_IF_NO_REF
+ */
+struct fragment *build_fragment_ref(char *ref, bool path_ref,
+				    struct srcpos *srcpos)
+{
+	struct fragment *new = build_fragment(FRAGMENT_REF, NULL);
+
+	new->ref = ref;
+	new->path_ref = path_ref;
+	new->ref_srcpos = srcpos_copy(srcpos);
+
+	return new;
+}
+
+/* Add a fragment to the end of a list, returning the new head */
+struct fragment *append_fragment(struct fragment *list, struct fragment *new)
+{
+	struct fragment *last;
+
+	new->next = NULL;
+
+	if (!list)
+		return new;
+
+	for (last = list; last->next; last = last->next)
+		;
+
+	last->next = new;
+
+	return list;
+}
+
+/*
+ * Free a fragment, but not its node, reference or label, since the tree may
+ * still use these
+ */
+void free_fragment(struct fragment *f)
+{
+	srcpos_free(f->ref_srcpos);
+	srcpos_free(f->label_srcpos);
+	free(f);
+}
+
 static void fill_fullpaths(struct node *tree, const char *prefix)
 {
 	struct node *child;
@@ -475,6 +534,7 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
 	dti->dtsflags = dtsflags;
 	dti->reservelist = reservelist;
 	dti->dt = tree;
+	dti->fragments = NULL;
 	dti->boot_cpuid_phys = boot_cpuid_phys;
 
 	fill_fullpaths(dti->dt, "");
-- 
2.43.0


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

* [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
  2026-11-01 16:36 ` [PATCH 01/13] tests: Check each error when merging a dts file Simon Glass
  2026-11-01 16:36 ` [PATCH 02/13] dtc: Add a list for the top-level fragments of " Simon Glass
@ 2026-11-01 16:36 ` Simon Glass
  2026-11-02  9:55   ` David Gibson
  2026-11-01 16:36 ` [PATCH 04/13] dtc: Move the /omit-if-no-ref/ " Simon Glass
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The grammar actions merge each top-level fragment of a dts file into the
tree as it is parsed, so the merge logic is spread over the rules for
each kind of fragment. This makes it hard to follow and to change.

As a first step towards moving this logic into livetree.c, move the
action for a top-level /delete-node/ into a new merge_delete_node(),
passing it a struct fragment for the reference. Add a fragment_ref rule
to build this.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 26 +++++++++++++++++---------
 dtc.h        |  1 +
 livetree.c   | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index e47f80d..61301a6 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -47,6 +47,7 @@ static bool is_ref_relative(const char *ref)
 	struct node *node;
 	struct node *nodelist;
 	struct reserve_info *re;
+	struct fragment *fragment;
 	uint64_t integer;
 	unsigned int flags;
 }
@@ -80,6 +81,7 @@ static bool is_ref_relative(const char *ref)
 %type <prop> propdef
 %type <proplist> proplist
 %type <labelref> dt_ref
+%type <fragment> fragment_ref
 
 %type <node> devicetree
 %type <node> nodedef
@@ -157,6 +159,17 @@ memreserve:
 
 dt_ref: DT_LABEL_REF | DT_PATH_REF;
 
+fragment_ref:
+	  DT_LABEL_REF
+		{
+			$$ = build_fragment_ref($1, false, &@$);
+		}
+	| DT_PATH_REF
+		{
+			$$ = build_fragment_ref($1, true, &@$);
+		}
+	;
+
 devicetree:
 	  '/' nodedef
 		{
@@ -236,16 +249,11 @@ devicetree:
 			}
 			$$ = $1;
 		}
-	| devicetree DT_DEL_NODE dt_ref ';'
+	| devicetree DT_DEL_NODE fragment_ref ';'
 		{
-			struct node *target = get_node_by_ref($1, $3);
-
-			if (target)
-				delete_node(target);
-			else
-				ERROR(&@3, "Label or path %s not found", $3);
-
-
+			$3->type = FRAGMENT_DELETE_NODE;
+			if (merge_delete_node($1, $3))
+				treesource_error = true;
 			$$ = $1;
 		}
 	| devicetree DT_OMIT_NO_REF dt_ref ';'
diff --git a/dtc.h b/dtc.h
index c0d8e8f..9f9076d 100644
--- a/dtc.h
+++ b/dtc.h
@@ -368,6 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 				    struct srcpos *srcpos);
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
+bool merge_delete_node(struct node *dt, struct fragment *f);
 
 struct dt_info {
 	unsigned int dtsflags;
diff --git a/livetree.c b/livetree.c
index 982f0a6..5c9ec48 100644
--- a/livetree.c
+++ b/livetree.c
@@ -542,6 +542,25 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
 	return dti;
 }
 
+/*
+ * Delete the node which a /delete-node/ fragment refers to
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_delete_node(struct node *dt, struct fragment *f)
+{
+	struct node *target = get_node_by_ref(dt, f->ref);
+
+	if (!target) {
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		return true;
+	}
+	delete_node(target);
+
+	return false;
+}
+
 /*
  * Tree accessor functions
  */
-- 
2.43.0


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

* [PATCH 04/13] dtc: Move the /omit-if-no-ref/ handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (2 preceding siblings ...)
  2026-11-01 16:36 ` [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar Simon Glass
@ 2026-11-01 16:36 ` Simon Glass
  2026-11-02 10:03   ` David Gibson
  2026-11-01 16:36 ` [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c Simon Glass
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The grammar action for a top-level /omit-if-no-ref/ still holds its
merge logic.

Move it into a new merge_omit_if_no_ref(), passing it a struct fragment
for the reference.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 13 ++++---------
 dtc.h        |  1 +
 livetree.c   | 20 ++++++++++++++++++++
 3 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 61301a6..089ab55 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -256,16 +256,11 @@ devicetree:
 				treesource_error = true;
 			$$ = $1;
 		}
-	| devicetree DT_OMIT_NO_REF dt_ref ';'
+	| devicetree DT_OMIT_NO_REF fragment_ref ';'
 		{
-			struct node *target = get_node_by_ref($1, $3);
-
-			if (target)
-				omit_node_if_unused(target);
-			else
-				ERROR(&@3, "Label or path %s not found", $3);
-
-
+			$3->type = FRAGMENT_OMIT_IF_NO_REF;
+			if (merge_omit_if_no_ref($1, $3))
+				treesource_error = true;
 			$$ = $1;
 		}
 	;
diff --git a/dtc.h b/dtc.h
index 9f9076d..e747dc0 100644
--- a/dtc.h
+++ b/dtc.h
@@ -369,6 +369,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
 bool merge_delete_node(struct node *dt, struct fragment *f);
+bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
 
 struct dt_info {
 	unsigned int dtsflags;
diff --git a/livetree.c b/livetree.c
index 5c9ec48..5ed2085 100644
--- a/livetree.c
+++ b/livetree.c
@@ -561,6 +561,26 @@ bool merge_delete_node(struct node *dt, struct fragment *f)
 	return false;
 }
 
+/*
+ * Mark the node which an /omit-if-no-ref/ fragment refers to, so that it is
+ * dropped if nothing refers to it
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
+{
+	struct node *target = get_node_by_ref(dt, f->ref);
+
+	if (!target) {
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		return true;
+	}
+	omit_node_if_unused(target);
+
+	return false;
+}
+
 /*
  * Tree accessor functions
  */
-- 
2.43.0


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

* [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (3 preceding siblings ...)
  2026-11-01 16:36 ` [PATCH 04/13] dtc: Move the /omit-if-no-ref/ " Simon Glass
@ 2026-11-01 16:36 ` Simon Glass
  2026-11-02 10:04   ` David Gibson
  2026-11-01 16:37 ` [PATCH 06/13] dtc: Move label-reference handling out of the grammar Simon Glass
                   ` (7 subsequent siblings)
  12 siblings, 1 reply; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:36 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The remaining grammar actions which merge a fragment use
is_ref_relative() to reject a label-relative reference in a plugin.

Move it to livetree.c, so that it is available when these actions move
there too, and add a comment.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 5 -----
 dtc.h        | 1 +
 livetree.c   | 6 ++++++
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 089ab55..7545895 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -24,11 +24,6 @@ extern void yyerror(char const *s);
 extern struct dt_info *parser_output;
 extern bool treesource_error;
 
-static bool is_ref_relative(const char *ref)
-{
-	return ref[0] != '/' && strchr(&ref[1], '/');
-}
-
 %}
 
 %union {
diff --git a/dtc.h b/dtc.h
index e747dc0..d145b03 100644
--- a/dtc.h
+++ b/dtc.h
@@ -368,6 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 				    struct srcpos *srcpos);
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
+bool is_ref_relative(const char *ref);
 bool merge_delete_node(struct node *dt, struct fragment *f);
 bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
 
diff --git a/livetree.c b/livetree.c
index 5ed2085..027682a 100644
--- a/livetree.c
+++ b/livetree.c
@@ -542,6 +542,12 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
 	return dti;
 }
 
+/* Check whether a reference is relative to a label, e.g. &{label/node} */
+bool is_ref_relative(const char *ref)
+{
+	return ref[0] != '/' && strchr(&ref[1], '/');
+}
+
 /*
  * Delete the node which a /delete-node/ fragment refers to
  *
-- 
2.43.0


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

* [PATCH 06/13] dtc: Move label-reference handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (4 preceding siblings ...)
  2026-11-01 16:36 ` [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 07/13] dtc: Move path-reference " Simon Glass
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The grammar action for a top-level &label { ... } holds its merge logic,
including making an overlay fragment in a plugin when the label is not
found.

Move it into a new merge_label_ref(), passing it a struct fragment for
the reference and node. The rule cannot use fragment_ref yet, since a
path reference still has its own rule, so it builds the fragment itself.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 23 +++++++++--------------
 dtc.h        |  1 +
 livetree.c   | 25 +++++++++++++++++++++++++
 3 files changed, 35 insertions(+), 14 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 7545895..1a5ad12 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -227,21 +227,16 @@ devicetree:
 		}
 	| devicetree DT_LABEL_REF nodedef
 		{
-			struct node *target = get_node_by_ref($1, $2);
+			struct fragment *f = build_fragment_ref($2, false, &@2);
 
-			if (target) {
-				merge_nodes(target, $3);
-			} else {
-				/*
-				 * We rely on the rule being always:
-				 *   versioninfo plugindecl memreserves devicetree
-				 * so $-1 is what we want (plugindecl)
-				 */
-				if ($<flags>-1 & DTSF_PLUGIN)
-					add_orphan_node($1, $3, $2);
-				else
-					ERROR(&@2, "Label or path %s not found", $2);
-			}
+			f->node = $3;
+			/*
+			 * We rely on the rule being always:
+			 *   versioninfo plugindecl memreserves devicetree
+			 * so $-1 is what we want (plugindecl)
+			 */
+			if (merge_label_ref($1, f, $<flags>-1 & DTSF_PLUGIN))
+				treesource_error = true;
 			$$ = $1;
 		}
 	| devicetree DT_DEL_NODE fragment_ref ';'
diff --git a/dtc.h b/dtc.h
index d145b03..256017f 100644
--- a/dtc.h
+++ b/dtc.h
@@ -369,6 +369,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
 bool is_ref_relative(const char *ref);
+bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin);
 bool merge_delete_node(struct node *dt, struct fragment *f);
 bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
 
diff --git a/livetree.c b/livetree.c
index 027682a..2a01d49 100644
--- a/livetree.c
+++ b/livetree.c
@@ -548,6 +548,31 @@ bool is_ref_relative(const char *ref)
 	return ref[0] != '/' && strchr(&ref[1], '/');
 }
 
+/*
+ * Merge a fragment which refers to a label into the node with that label. In
+ * a plugin, a label which is not found becomes an overlay fragment.
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin)
+{
+	struct node *target = get_node_by_ref(dt, f->ref);
+
+	if (target) {
+		merge_nodes(target, f->node);
+		return false;
+	}
+	if (!plugin) {
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		return true;
+	}
+	/* The label may be in the base tree, so add a fragment@N node for it */
+	add_orphan_node(dt, f->node, f->ref);
+
+	return false;
+}
+
 /*
  * Delete the node which a /delete-node/ fragment refers to
  *
-- 
2.43.0


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

* [PATCH 07/13] dtc: Move path-reference handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (5 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 06/13] dtc: Move label-reference handling out of the grammar Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 08/13] dtc: Move labelled-reference " Simon Glass
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The grammar action for a top-level &{/path} { ... } holds its merge
logic, including making an overlay fragment in a plugin.

Move it into a new merge_path_ref(), passing it a struct fragment for
the reference and node, built in the same way as for a label reference.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 17 +++++------------
 dtc.h        |  1 +
 livetree.c   | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 1a5ad12..cca3ce1 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -206,23 +206,16 @@ devicetree:
 		}
 	| devicetree DT_PATH_REF nodedef
 		{
+			struct fragment *f = build_fragment_ref($2, true, &@2);
+
+			f->node = $3;
 			/*
 			 * We rely on the rule being always:
 			 *   versioninfo plugindecl memreserves devicetree
 			 * so $-1 is what we want (plugindecl)
 			 */
-			if ($<flags>-1 & DTSF_PLUGIN) {
-				if (is_ref_relative($2))
-					ERROR(&@2, "Label-relative reference %s not supported in plugin", $2);
-				add_orphan_node($1, $3, $2);
-			} else {
-				struct node *target = get_node_by_ref($1, $2);
-
-				if (target)
-					merge_nodes(target, $3);
-				else
-					ERROR(&@2, "Label or path %s not found", $2);
-			}
+			if (merge_path_ref($1, f, $<flags>-1 & DTSF_PLUGIN))
+				treesource_error = true;
 			$$ = $1;
 		}
 	| devicetree DT_LABEL_REF nodedef
diff --git a/dtc.h b/dtc.h
index 256017f..5af1d1f 100644
--- a/dtc.h
+++ b/dtc.h
@@ -369,6 +369,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
 bool is_ref_relative(const char *ref);
+bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin);
 bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin);
 bool merge_delete_node(struct node *dt, struct fragment *f);
 bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
diff --git a/livetree.c b/livetree.c
index 2a01d49..b0362e4 100644
--- a/livetree.c
+++ b/livetree.c
@@ -548,6 +548,42 @@ bool is_ref_relative(const char *ref)
 	return ref[0] != '/' && strchr(&ref[1], '/');
 }
 
+/*
+ * Merge a fragment which refers to a path into the node at that path. In a
+ * plugin, the fragment becomes an overlay fragment instead.
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin)
+{
+	struct node *target;
+
+	if (plugin) {
+		bool error = false;
+
+		if (is_ref_relative(f->ref)) {
+			srcpos_error(f->ref_srcpos, "Error",
+				     "Label-relative reference %s not supported in plugin",
+				     f->ref);
+			error = true;
+		}
+		/* Add a fragment@N node which applies the node at the path */
+		add_orphan_node(dt, f->node, f->ref);
+
+		return error;
+	}
+
+	target = get_node_by_ref(dt, f->ref);
+	if (!target) {
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		return true;
+	}
+	merge_nodes(target, f->node);
+
+	return false;
+}
+
 /*
  * Merge a fragment which refers to a label into the node with that label. In
  * a plugin, a label which is not found becomes an overlay fragment.
-- 
2.43.0


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

* [PATCH 08/13] dtc: Move labelled-reference handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (6 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 07/13] dtc: Move path-reference " Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 09/13] dtc: Move first-reference " Simon Glass
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

The grammar action for a top-level label: &ref { ... } holds its merge
logic, which adds the label to the node referred to.

Move it into a new merge_labelled_ref(), passing it a struct fragment
from fragment_ref, with the label and node added.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 18 +++++++-----------
 dtc.h        |  2 ++
 livetree.c   | 28 ++++++++++++++++++++++++++++
 3 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index cca3ce1..0e6c4c2 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -190,18 +190,14 @@ devicetree:
 						  ""),
 					$2, $1);
 		}
-	| devicetree DT_LABEL dt_ref nodedef
+	| devicetree DT_LABEL fragment_ref nodedef
 		{
-			struct node *target = get_node_by_ref($1, $3);
-
-			if (($<flags>-1 & DTSF_PLUGIN) && is_ref_relative($3))
-				ERROR(&@2, "Label-relative reference %s not supported in plugin", $3);
-
-			if (target) {
-				add_label(&target->labels, $2);
-				merge_nodes(target, $4);
-			} else
-				ERROR(&@3, "Label or path %s not found", $3);
+			$3->label = $2;
+			$3->label_srcpos = srcpos_copy(&@2);
+			$3->node = $4;
+			if (merge_labelled_ref($1, $3,
+					       $<flags>-1 & DTSF_PLUGIN))
+				treesource_error = true;
 			$$ = $1;
 		}
 	| devicetree DT_PATH_REF nodedef
diff --git a/dtc.h b/dtc.h
index 5af1d1f..75b1c0f 100644
--- a/dtc.h
+++ b/dtc.h
@@ -369,6 +369,8 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
 bool is_ref_relative(const char *ref);
+bool merge_labelled_ref(struct node *dt, struct fragment *f,
+			bool plugin);
 bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin);
 bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin);
 bool merge_delete_node(struct node *dt, struct fragment *f);
diff --git a/livetree.c b/livetree.c
index b0362e4..76474cd 100644
--- a/livetree.c
+++ b/livetree.c
@@ -548,6 +548,34 @@ bool is_ref_relative(const char *ref)
 	return ref[0] != '/' && strchr(&ref[1], '/');
 }
 
+/*
+ * Merge a fragment of the form 'label: &ref { ... }' into the node which ref
+ * refers to, adding the label to that node
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_labelled_ref(struct node *dt, struct fragment *f, bool plugin)
+{
+	struct node *target = get_node_by_ref(dt, f->ref);
+	bool error = false;
+
+	if (plugin && is_ref_relative(f->ref)) {
+		srcpos_error(f->label_srcpos, "Error",
+			     "Label-relative reference %s not supported in plugin",
+			     f->ref);
+		error = true;
+	}
+	if (!target) {
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		return true;
+	}
+	add_label(&target->labels, f->label);
+	merge_nodes(target, f->node);
+
+	return error;
+}
+
 /*
  * Merge a fragment which refers to a path into the node at that path. In a
  * plugin, the fragment becomes an overlay fragment instead.
-- 
2.43.0


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

* [PATCH 09/13] dtc: Move first-reference handling out of the grammar
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (7 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 08/13] dtc: Move labelled-reference " Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 10/13] dtc: Add merge_fragment() to merge any fragment Simon Glass
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

In a plugin, the first top-level fragment can refer to a label or path
instead of being the root node. The grammar action for this holds its
merge logic, which makes an overlay fragment in a new tree.

Move it into a new merge_first_ref(), passing it a struct fragment from
fragment_ref, with the node added. This is the last user of
is_ref_relative() in the grammar, so make that function static.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 13 ++++---------
 dtc.h        |  2 +-
 livetree.c   | 30 +++++++++++++++++++++++++++++-
 3 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 0e6c4c2..32f7c26 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -174,21 +174,16 @@ devicetree:
 		{
 			$$ = merge_nodes($1, $3);
 		}
-	| dt_ref nodedef
+	| fragment_ref nodedef
 		{
+			$1->node = $2;
 			/*
 			 * We rely on the rule being always:
 			 *   versioninfo plugindecl memreserves devicetree
 			 * so $-1 is what we want (plugindecl)
 			 */
-			if (!($<flags>-1 & DTSF_PLUGIN))
-				ERROR(&@2, "Label or path %s not found", $1);
-			else if (is_ref_relative($1))
-				ERROR(&@2, "Label-relative reference %s not supported in plugin", $1);
-			$$ = add_orphan_node(
-					name_node(build_node(NULL, NULL, NULL),
-						  ""),
-					$2, $1);
+			if (merge_first_ref(&$$, $1, $<flags>-1 & DTSF_PLUGIN))
+				treesource_error = true;
 		}
 	| devicetree DT_LABEL fragment_ref nodedef
 		{
diff --git a/dtc.h b/dtc.h
index 75b1c0f..32e4394 100644
--- a/dtc.h
+++ b/dtc.h
@@ -368,7 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 				    struct srcpos *srcpos);
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
-bool is_ref_relative(const char *ref);
+bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin);
 bool merge_labelled_ref(struct node *dt, struct fragment *f,
 			bool plugin);
 bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin);
diff --git a/livetree.c b/livetree.c
index 76474cd..f2244e2 100644
--- a/livetree.c
+++ b/livetree.c
@@ -543,11 +543,39 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
 }
 
 /* Check whether a reference is relative to a label, e.g. &{label/node} */
-bool is_ref_relative(const char *ref)
+static bool is_ref_relative(const char *ref)
 {
 	return ref[0] != '/' && strchr(&ref[1], '/');
 }
 
+/*
+ * Make the tree at *dtp from a first fragment which refers to a label or path,
+ * rather than being the root node. This is only valid in a plugin, where the
+ * fragment becomes an overlay fragment.
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
+{
+	bool error = false;
+
+	if (!plugin) {
+		srcpos_error(f->node->srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+		error = true;
+	} else if (is_ref_relative(f->ref)) {
+		srcpos_error(f->node->srcpos, "Error",
+			     "Label-relative reference %s not supported in plugin",
+			     f->ref);
+		error = true;
+	}
+	/* Make a new, empty tree with a fragment@N node for the reference */
+	*dtp = add_orphan_node(name_node(build_node(NULL, NULL, NULL), ""),
+			       f->node, f->ref);
+
+	return error;
+}
+
 /*
  * Merge a fragment of the form 'label: &ref { ... }' into the node which ref
  * refers to, adding the label to that node
-- 
2.43.0


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

* [PATCH 10/13] dtc: Add merge_fragment() to merge any fragment
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (8 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 09/13] dtc: Move first-reference " Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 11/13] dtc: Add helpers for the errors when merging a fragment Simon Glass
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

Each grammar action which merges a fragment calls the function for its
own kind of fragment, sets treesource_error if that fails, and finds out
whether the file is a plugin using $<flags>-1

Add merge_fragment() to merge any fragment, choosing the function by the
type of fragment and whether it is the first. Have each action build a
struct fragment and pass it to a new merge_parsed_fragment() helper,
which calls merge_fragment(), sets treesource_error and frees the
fragment. The two rules for a plain label or path reference become one,
using fragment_ref, and the functions used by merge_fragment() become
static.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 74 ++++++++++++++++++++--------------------------------
 dtc.h        |  8 +-----
 livetree.c   | 50 ++++++++++++++++++++++++++++++-----
 3 files changed, 74 insertions(+), 58 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 32f7c26..1ed9731 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -24,6 +24,22 @@ extern void yyerror(char const *s);
 extern struct dt_info *parser_output;
 extern bool treesource_error;
 
+/*
+ * Merge a fragment into the tree as soon as it is parsed. The flags come
+ * from the actions using $<flags>-1, which relies on the rule being always:
+ *   headers memreserves devicetree
+ * so $-1 is the value of headers
+ */
+static struct node *merge_parsed_fragment(struct node *dt, struct fragment *f,
+					  unsigned int flags)
+{
+	if (merge_fragment(&dt, f, flags & DTSF_PLUGIN))
+		treesource_error = true;
+	free_fragment(f);
+
+	return dt;
+}
+
 %}
 
 %union {
@@ -168,74 +184,42 @@ fragment_ref:
 devicetree:
 	  '/' nodedef
 		{
-			$$ = name_node($2, "");
+			struct fragment *f = build_fragment(FRAGMENT_ROOT, $2);
+
+			$$ = merge_parsed_fragment(NULL, f, $<flags>-1);
 		}
 	| devicetree '/' nodedef
 		{
-			$$ = merge_nodes($1, $3);
+			struct fragment *f = build_fragment(FRAGMENT_ROOT, $3);
+
+			$$ = merge_parsed_fragment($1, f, $<flags>-1);
 		}
 	| fragment_ref nodedef
 		{
 			$1->node = $2;
-			/*
-			 * We rely on the rule being always:
-			 *   versioninfo plugindecl memreserves devicetree
-			 * so $-1 is what we want (plugindecl)
-			 */
-			if (merge_first_ref(&$$, $1, $<flags>-1 & DTSF_PLUGIN))
-				treesource_error = true;
+			$$ = merge_parsed_fragment(NULL, $1, $<flags>-1);
 		}
 	| devicetree DT_LABEL fragment_ref nodedef
 		{
 			$3->label = $2;
 			$3->label_srcpos = srcpos_copy(&@2);
 			$3->node = $4;
-			if (merge_labelled_ref($1, $3,
-					       $<flags>-1 & DTSF_PLUGIN))
-				treesource_error = true;
-			$$ = $1;
+			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
 		}
-	| devicetree DT_PATH_REF nodedef
+	| devicetree fragment_ref nodedef
 		{
-			struct fragment *f = build_fragment_ref($2, true, &@2);
-
-			f->node = $3;
-			/*
-			 * We rely on the rule being always:
-			 *   versioninfo plugindecl memreserves devicetree
-			 * so $-1 is what we want (plugindecl)
-			 */
-			if (merge_path_ref($1, f, $<flags>-1 & DTSF_PLUGIN))
-				treesource_error = true;
-			$$ = $1;
-		}
-	| devicetree DT_LABEL_REF nodedef
-		{
-			struct fragment *f = build_fragment_ref($2, false, &@2);
-
-			f->node = $3;
-			/*
-			 * We rely on the rule being always:
-			 *   versioninfo plugindecl memreserves devicetree
-			 * so $-1 is what we want (plugindecl)
-			 */
-			if (merge_label_ref($1, f, $<flags>-1 & DTSF_PLUGIN))
-				treesource_error = true;
-			$$ = $1;
+			$2->node = $3;
+			$$ = merge_parsed_fragment($1, $2, $<flags>-1);
 		}
 	| devicetree DT_DEL_NODE fragment_ref ';'
 		{
 			$3->type = FRAGMENT_DELETE_NODE;
-			if (merge_delete_node($1, $3))
-				treesource_error = true;
-			$$ = $1;
+			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
 		}
 	| devicetree DT_OMIT_NO_REF fragment_ref ';'
 		{
 			$3->type = FRAGMENT_OMIT_IF_NO_REF;
-			if (merge_omit_if_no_ref($1, $3))
-				treesource_error = true;
-			$$ = $1;
+			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
 		}
 	;
 
diff --git a/dtc.h b/dtc.h
index 32e4394..41013f5 100644
--- a/dtc.h
+++ b/dtc.h
@@ -368,13 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 				    struct srcpos *srcpos);
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
-bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin);
-bool merge_labelled_ref(struct node *dt, struct fragment *f,
-			bool plugin);
-bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin);
-bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin);
-bool merge_delete_node(struct node *dt, struct fragment *f);
-bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
+bool merge_fragment(struct node **dtp, struct fragment *f, bool plugin);
 
 struct dt_info {
 	unsigned int dtsflags;
diff --git a/livetree.c b/livetree.c
index f2244e2..6a290fb 100644
--- a/livetree.c
+++ b/livetree.c
@@ -555,7 +555,7 @@ static bool is_ref_relative(const char *ref)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
+static bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
 {
 	bool error = false;
 
@@ -582,7 +582,7 @@ bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_labelled_ref(struct node *dt, struct fragment *f, bool plugin)
+static bool merge_labelled_ref(struct node *dt, struct fragment *f, bool plugin)
 {
 	struct node *target = get_node_by_ref(dt, f->ref);
 	bool error = false;
@@ -610,7 +610,7 @@ bool merge_labelled_ref(struct node *dt, struct fragment *f, bool plugin)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin)
+static bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin)
 {
 	struct node *target;
 
@@ -646,7 +646,7 @@ bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin)
+static bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin)
 {
 	struct node *target = get_node_by_ref(dt, f->ref);
 
@@ -670,7 +670,7 @@ bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_delete_node(struct node *dt, struct fragment *f)
+static bool merge_delete_node(struct node *dt, struct fragment *f)
 {
 	struct node *target = get_node_by_ref(dt, f->ref);
 
@@ -690,7 +690,7 @@ bool merge_delete_node(struct node *dt, struct fragment *f)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
+static bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
 {
 	struct node *target = get_node_by_ref(dt, f->ref);
 
@@ -704,6 +704,44 @@ bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
 	return false;
 }
 
+/*
+ * Merge a fragment of a dts file into the tree at *dtp, which is NULL for the
+ * first fragment. A reference is looked up in the tree as merged so far.
+ *
+ * Returns true if there is an error, which has been reported.
+ */
+bool merge_fragment(struct node **dtp, struct fragment *f, bool plugin)
+{
+	struct node *dt = *dtp;
+
+	if (!dt) {
+		/* The first fragment is the root or, in a plugin, a ref */
+		if (f->type == FRAGMENT_ROOT) {
+			*dtp = name_node(f->node, "");
+			return false;
+		}
+		return merge_first_ref(dtp, f, plugin);
+	}
+
+	switch (f->type) {
+	case FRAGMENT_ROOT:
+		*dtp = merge_nodes(dt, f->node);
+		break;
+	case FRAGMENT_REF:
+		if (f->label)
+			return merge_labelled_ref(dt, f, plugin);
+		if (f->path_ref)
+			return merge_path_ref(dt, f, plugin);
+		return merge_label_ref(dt, f, plugin);
+	case FRAGMENT_DELETE_NODE:
+		return merge_delete_node(dt, f);
+	case FRAGMENT_OMIT_IF_NO_REF:
+		return merge_omit_if_no_ref(dt, f);
+	}
+
+	return false;
+}
+
 /*
  * Tree accessor functions
  */
-- 
2.43.0


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

* [PATCH 11/13] dtc: Add helpers for the errors when merging a fragment
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (9 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 10/13] dtc: Add merge_fragment() to merge any fragment Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 12/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
  2026-11-01 16:37 ` [PATCH 13/13] dtc: Merge the fragments of a dts file in main() Simon Glass
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

Four of the merge functions look up the node which a fragment refers to
and report 'Label or path not found' if it is missing, and three check
for a label-relative reference in a plugin. Each of these has its own
copy of the error, so a change to a message or its position must be made
in several places.

Add lookup_ref() to look up the node, reporting an error if it is not
found, and check_relative_ref() to report a label-relative reference.
Use these in the merge functions.

The output does not change.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 livetree.c | 90 +++++++++++++++++++++++++++++-------------------------
 1 file changed, 49 insertions(+), 41 deletions(-)

diff --git a/livetree.c b/livetree.c
index 6a290fb..1e012d4 100644
--- a/livetree.c
+++ b/livetree.c
@@ -548,6 +548,40 @@ static bool is_ref_relative(const char *ref)
 	return ref[0] != '/' && strchr(&ref[1], '/');
 }
 
+/*
+ * Report an error at pos if a fragment uses a label-relative reference, which
+ * a plugin does not support
+ *
+ * Returns true if there is an error
+ */
+static bool check_relative_ref(struct fragment *f, struct srcpos *pos)
+{
+	if (!is_ref_relative(f->ref))
+		return false;
+	srcpos_error(pos, "Error",
+		     "Label-relative reference %s not supported in plugin",
+		     f->ref);
+
+	return true;
+}
+
+/*
+ * Look up the node which a fragment refers to, reporting an error if it is
+ * not found
+ *
+ * Returns the node, or NULL if it is not found
+ */
+static struct node *lookup_ref(struct node *dt, struct fragment *f)
+{
+	struct node *target = get_node_by_ref(dt, f->ref);
+
+	if (!target)
+		srcpos_error(f->ref_srcpos, "Error",
+			     "Label or path %s not found", f->ref);
+
+	return target;
+}
+
 /*
  * Make the tree at *dtp from a first fragment which refers to a label or path,
  * rather than being the root node. This is only valid in a plugin, where the
@@ -557,17 +591,14 @@ static bool is_ref_relative(const char *ref)
  */
 static bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
 {
-	bool error = false;
+	bool error;
 
-	if (!plugin) {
+	if (plugin) {
+		error = check_relative_ref(f, f->node->srcpos);
+	} else {
 		srcpos_error(f->node->srcpos, "Error",
 			     "Label or path %s not found", f->ref);
 		error = true;
-	} else if (is_ref_relative(f->ref)) {
-		srcpos_error(f->node->srcpos, "Error",
-			     "Label-relative reference %s not supported in plugin",
-			     f->ref);
-		error = true;
 	}
 	/* Make a new, empty tree with a fragment@N node for the reference */
 	*dtp = add_orphan_node(name_node(build_node(NULL, NULL, NULL), ""),
@@ -584,20 +615,12 @@ static bool merge_first_ref(struct node **dtp, struct fragment *f, bool plugin)
  */
 static bool merge_labelled_ref(struct node *dt, struct fragment *f, bool plugin)
 {
-	struct node *target = get_node_by_ref(dt, f->ref);
-	bool error = false;
+	struct node *target;
+	bool error = plugin && check_relative_ref(f, f->label_srcpos);
 
-	if (plugin && is_ref_relative(f->ref)) {
-		srcpos_error(f->label_srcpos, "Error",
-			     "Label-relative reference %s not supported in plugin",
-			     f->ref);
-		error = true;
-	}
-	if (!target) {
-		srcpos_error(f->ref_srcpos, "Error",
-			     "Label or path %s not found", f->ref);
+	target = lookup_ref(dt, f);
+	if (!target)
 		return true;
-	}
 	add_label(&target->labels, f->label);
 	merge_nodes(target, f->node);
 
@@ -615,26 +638,17 @@ static bool merge_path_ref(struct node *dt, struct fragment *f, bool plugin)
 	struct node *target;
 
 	if (plugin) {
-		bool error = false;
+		bool error = check_relative_ref(f, f->ref_srcpos);
 
-		if (is_ref_relative(f->ref)) {
-			srcpos_error(f->ref_srcpos, "Error",
-				     "Label-relative reference %s not supported in plugin",
-				     f->ref);
-			error = true;
-		}
 		/* Add a fragment@N node which applies the node at the path */
 		add_orphan_node(dt, f->node, f->ref);
 
 		return error;
 	}
 
-	target = get_node_by_ref(dt, f->ref);
-	if (!target) {
-		srcpos_error(f->ref_srcpos, "Error",
-			     "Label or path %s not found", f->ref);
+	target = lookup_ref(dt, f);
+	if (!target)
 		return true;
-	}
 	merge_nodes(target, f->node);
 
 	return false;
@@ -672,13 +686,10 @@ static bool merge_label_ref(struct node *dt, struct fragment *f, bool plugin)
  */
 static bool merge_delete_node(struct node *dt, struct fragment *f)
 {
-	struct node *target = get_node_by_ref(dt, f->ref);
+	struct node *target = lookup_ref(dt, f);
 
-	if (!target) {
-		srcpos_error(f->ref_srcpos, "Error",
-			     "Label or path %s not found", f->ref);
+	if (!target)
 		return true;
-	}
 	delete_node(target);
 
 	return false;
@@ -692,13 +703,10 @@ static bool merge_delete_node(struct node *dt, struct fragment *f)
  */
 static bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
 {
-	struct node *target = get_node_by_ref(dt, f->ref);
+	struct node *target = lookup_ref(dt, f);
 
-	if (!target) {
-		srcpos_error(f->ref_srcpos, "Error",
-			     "Label or path %s not found", f->ref);
+	if (!target)
 		return true;
-	}
 	omit_node_if_unused(target);
 
 	return false;
-- 
2.43.0


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

* [PATCH 12/13] dtc: Merge the fragments of a dts file after parsing
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (10 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 11/13] dtc: Add helpers for the errors when merging a fragment Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  2026-11-01 16:37 ` [PATCH 13/13] dtc: Merge the fragments of a dts file in main() Simon Glass
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

We would like to deal with each top-level fragment of a dts file before
it is merged into the tree, but that requires the fragments to be kept
apart until the whole file is parsed. At present the parser merges each
one as soon as it is parsed. The grammar actions also need to know
whether the file is a plugin, which they find using $<flags>-1

Have the grammar build a list of the fragments instead, in source order,
and merge them with a new merge_fragments() once the whole file is
parsed. This calls merge_fragment() for each one, so the output does not
change, and frees each fragment once it is merged.

The $<flags>-1 term is no longer needed. Within a devicetree action it
reaches back up the parser stack to the value of headers, i.e. the flags
from /dts-v1/ and /plugin/. The sourcefile action passes the same value
to build_dt_info(), which stores it in dti->dtsflags, so
merge_fragments() can use that instead.

There are two differences in the errors. A syntax error later in the
file now stops dtc before any earlier reference errors are reported, and
reference errors now come after any other errors which the parser
reports, such as a division by zero, rather than in source order.

dtc gives the same output and messages as before for all 4162 .dts and
.dtso files in Linux v7.3-12284-g3799eeb906c9, and for the dts files in
tests/

Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Link: https://lore.kernel.org/devicetree-compiler/auK0ERvxGCJGSc3p@gractus.seuss/
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y | 43 +++++++++++++------------------------------
 dtc.h        |  2 +-
 livetree.c   | 36 ++++++++++++++++++++++++++++++++++--
 3 files changed, 48 insertions(+), 33 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index 1ed9731..c1aa4ad 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -24,22 +24,6 @@ extern void yyerror(char const *s);
 extern struct dt_info *parser_output;
 extern bool treesource_error;
 
-/*
- * Merge a fragment into the tree as soon as it is parsed. The flags come
- * from the actions using $<flags>-1, which relies on the rule being always:
- *   headers memreserves devicetree
- * so $-1 is the value of headers
- */
-static struct node *merge_parsed_fragment(struct node *dt, struct fragment *f,
-					  unsigned int flags)
-{
-	if (merge_fragment(&dt, f, flags & DTSF_PLUGIN))
-		treesource_error = true;
-	free_fragment(f);
-
-	return dt;
-}
-
 %}
 
 %union {
@@ -94,7 +78,7 @@ static struct node *merge_parsed_fragment(struct node *dt, struct fragment *f,
 %type <labelref> dt_ref
 %type <fragment> fragment_ref
 
-%type <node> devicetree
+%type <fragment> devicetree
 %type <node> nodedef
 %type <node> subnode
 %type <nodelist> subnodes
@@ -119,8 +103,10 @@ static struct node *merge_parsed_fragment(struct node *dt, struct fragment *f,
 sourcefile:
 	  headers memreserves devicetree
 		{
-			parser_output = build_dt_info($1, $2, $3,
-			                              guess_boot_cpuid($3));
+			parser_output = build_dt_info($1, $2, NULL, 0);
+			parser_output->fragments = $3;
+			if (merge_fragments(parser_output))
+				treesource_error = true;
 		}
 	;
 
@@ -184,42 +170,39 @@ fragment_ref:
 devicetree:
 	  '/' nodedef
 		{
-			struct fragment *f = build_fragment(FRAGMENT_ROOT, $2);
-
-			$$ = merge_parsed_fragment(NULL, f, $<flags>-1);
+			$$ = build_fragment(FRAGMENT_ROOT, $2);
 		}
 	| devicetree '/' nodedef
 		{
-			struct fragment *f = build_fragment(FRAGMENT_ROOT, $3);
-
-			$$ = merge_parsed_fragment($1, f, $<flags>-1);
+			$$ = append_fragment($1, build_fragment(FRAGMENT_ROOT,
+								$3));
 		}
 	| fragment_ref nodedef
 		{
 			$1->node = $2;
-			$$ = merge_parsed_fragment(NULL, $1, $<flags>-1);
+			$$ = $1;
 		}
 	| devicetree DT_LABEL fragment_ref nodedef
 		{
 			$3->label = $2;
 			$3->label_srcpos = srcpos_copy(&@2);
 			$3->node = $4;
-			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
+			$$ = append_fragment($1, $3);
 		}
 	| devicetree fragment_ref nodedef
 		{
 			$2->node = $3;
-			$$ = merge_parsed_fragment($1, $2, $<flags>-1);
+			$$ = append_fragment($1, $2);
 		}
 	| devicetree DT_DEL_NODE fragment_ref ';'
 		{
 			$3->type = FRAGMENT_DELETE_NODE;
-			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
+			$$ = append_fragment($1, $3);
 		}
 	| devicetree DT_OMIT_NO_REF fragment_ref ';'
 		{
 			$3->type = FRAGMENT_OMIT_IF_NO_REF;
-			$$ = merge_parsed_fragment($1, $3, $<flags>-1);
+			$$ = append_fragment($1, $3);
 		}
 	;
 
diff --git a/dtc.h b/dtc.h
index 41013f5..fdd0d75 100644
--- a/dtc.h
+++ b/dtc.h
@@ -368,7 +368,6 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
 				    struct srcpos *srcpos);
 struct fragment *append_fragment(struct fragment *list, struct fragment *new);
 void free_fragment(struct fragment *f);
-bool merge_fragment(struct node **dtp, struct fragment *f, bool plugin);
 
 struct dt_info {
 	unsigned int dtsflags;
@@ -386,6 +385,7 @@ struct dt_info {
 struct dt_info *build_dt_info(unsigned int dtsflags,
 			      struct reserve_info *reservelist,
 			      struct node *tree, uint32_t boot_cpuid_phys);
+bool merge_fragments(struct dt_info *dti);
 void sort_tree(struct dt_info *dti);
 void generate_labels_from_tree(struct dt_info *dti, const char *name);
 void generate_label_tree(struct dt_info *dti, const char *name, bool allocph);
diff --git a/livetree.c b/livetree.c
index 1e012d4..68ad3c3 100644
--- a/livetree.c
+++ b/livetree.c
@@ -537,7 +537,8 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
 	dti->fragments = NULL;
 	dti->boot_cpuid_phys = boot_cpuid_phys;
 
-	fill_fullpaths(dti->dt, "");
+	if (tree)
+		fill_fullpaths(dti->dt, "");
 
 	return dti;
 }
@@ -718,7 +719,8 @@ static bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
  *
  * Returns true if there is an error, which has been reported.
  */
-bool merge_fragment(struct node **dtp, struct fragment *f, bool plugin)
+static bool merge_fragment(struct node **dtp, struct fragment *f,
+			   bool plugin)
 {
 	struct node *dt = *dtp;
 
@@ -750,6 +752,36 @@ bool merge_fragment(struct node **dtp, struct fragment *f, bool plugin)
 	return false;
 }
 
+/*
+ * Merge the fragments of a dts file into a single tree, in source order.
+ *
+ * Returns true if there are errors, which have been reported.
+ */
+bool merge_fragments(struct dt_info *dti)
+{
+	bool plugin = dti->dtsflags & DTSF_PLUGIN;
+	struct fragment *f, *next;
+	struct node *dt = NULL;
+	bool error = false;
+
+	if (!dti->fragments)
+		return false;
+
+	for (f = dti->fragments; f; f = next) {
+		next = f->next;
+		if (merge_fragment(&dt, f, plugin))
+			error = true;
+		free_fragment(f);
+	}
+
+	dti->dt = dt;
+	dti->fragments = NULL;
+	dti->boot_cpuid_phys = guess_boot_cpuid(dt);
+	fill_fullpaths(dti->dt, "");
+
+	return error;
+}
+
 /*
  * Tree accessor functions
  */
-- 
2.43.0


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

* [PATCH 13/13] dtc: Merge the fragments of a dts file in main()
  2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
                   ` (11 preceding siblings ...)
  2026-11-01 16:37 ` [PATCH 12/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
@ 2026-11-01 16:37 ` Simon Glass
  12 siblings, 0 replies; 19+ messages in thread
From: Simon Glass @ 2026-11-01 16:37 UTC (permalink / raw)
  To: Devicetree Compiler; +Cc: David Gibson, Simon Glass

We would like to run checks on each fragment of a dts file before it is
merged, but that requires dt_from_source() to return the fragments
unmerged, so that main() can decide what to do with them. At present
they are merged at the end of the parse.

Call merge_fragments() from main() instead, once the input is read. It
does nothing for dtb or fs input, which has no fragments.

When a reference cannot be resolved, dtc now reports 'Unable to merge
input tree' rather than 'Syntax error parsing input tree', since the
file was parsed successfully. Check for this message with
test_label_ref.dts, since no test checks it at present.

If the parser reports an error, dt_from_source() gives up before main()
can merge the fragments. Merge them first in that case, so that any
errors in the fragments are still reported.

Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Link: https://lore.kernel.org/devicetree-compiler/auK0ERvxGCJGSc3p@gractus.seuss/
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 dtc-parser.y       | 2 --
 dtc.c              | 2 ++
 tests/run_tests.sh | 3 ++-
 treesource.c       | 5 ++++-
 4 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/dtc-parser.y b/dtc-parser.y
index c1aa4ad..f547a62 100644
--- a/dtc-parser.y
+++ b/dtc-parser.y
@@ -105,8 +105,6 @@ sourcefile:
 		{
 			parser_output = build_dt_info($1, $2, NULL, 0);
 			parser_output->fragments = $3;
-			if (merge_fragments(parser_output))
-				treesource_error = true;
 		}
 	;
 
diff --git a/dtc.c b/dtc.c
index e506837..787c724 100644
--- a/dtc.c
+++ b/dtc.c
@@ -299,6 +299,8 @@ int main(int argc, char *argv[])
 	else
 		die("Unknown input format \"%s\"\n", inform);
 
+	if (merge_fragments(dti))
+		die("Unable to merge input tree\n");
 	dti->outname = outname;
 
 	if (depfile) {
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index e0504c0..4ffd919 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -737,7 +737,8 @@ dtc_tests () {
     run_test dtbs_equal_ordered multilabel.test.dtb multilabel_merge.test.dtb
     run_dtc_test -I dts -O dtb -o dtc_tree1_merge_path.test.dtb "$SRCDIR/test_tree1_merge_path.dts"
     tree1_tests dtc_tree1_merge_path.test.dtb test_tree1.dtb
-    run_wrap_error_test $DTC -I dts -O dtb -o /dev/null "$SRCDIR/test_label_ref.dts"
+    run_wrap_error_msg_test "Unable to merge input tree" \
+	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/test_label_ref.dts"
 
     run_dtc_test -I dts -O dtb -o dtc_relref_merge.test.dtb "$SRCDIR/relref_merge.dts"
     run_test relref_merge dtc_relref_merge.test.dtb
diff --git a/treesource.c b/treesource.c
index bf648bf..3fb9d83 100644
--- a/treesource.c
+++ b/treesource.c
@@ -25,8 +25,11 @@ struct dt_info *dt_from_source(const char *fname)
 	if (yyparse() != 0)
 		die("Unable to parse input tree\n");
 
-	if (treesource_error)
+	if (treesource_error) {
+		/* Report any errors in the fragments as well, before giving up */
+		merge_fragments(parser_output);
 		die("Syntax error parsing input tree\n");
+	}
 
 	return parser_output;
 }
-- 
2.43.0


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

* Re: [PATCH 01/13] tests: Check each error when merging a dts file
  2026-11-01 16:36 ` [PATCH 01/13] tests: Check each error when merging a dts file Simon Glass
@ 2026-11-02  6:32   ` David Gibson
  0 siblings, 0 replies; 19+ messages in thread
From: David Gibson @ 2026-11-02  6:32 UTC (permalink / raw)
  To: Simon Glass; +Cc: Devicetree Compiler

[-- Attachment #1: Type: text/plain, Size: 10616 bytes --]

On Sun, Nov 01, 2026 at 09:36:55AM -0700, Simon Glass wrote:
> When the parser merges the top-level fragments of a dts file, it can
> report that a label or path is not found, or that a label-relative
> reference is not supported in a plugin. Most of these errors are not
> covered by any test, and the two tests which do reach them only check
> that dtc fails, not the message or where it is reported. The merge
> logic is about to move out of the grammar, so this needs better tests.
> 
> Add a test file for each error which is not covered, and check the
> message and its position for all of them, including the two existing
> ones. Also check that merging carries on after an error, so that later
> errors are reported too, that a reference error is still reported when
> the parser finds another error, and that a labelled reference within a
> plugin adds the label, since nothing covers these either.
> 
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>

Couple of nits, otherwise LGTM.

> ---
> 
>  tests/merge-delete-missing.dts     |  9 +++++++
>  tests/merge-errors.dts             | 14 ++++++++++
>  tests/merge-first-ref-relative.dts | 10 ++++++++
>  tests/merge-first-ref.dts          |  9 +++++++
>  tests/merge-labelled-plugin.dts    | 12 +++++++++
>  tests/merge-labelled-relative.dts  | 14 ++++++++++
>  tests/merge-omit-missing.dts       |  9 +++++++
>  tests/merge-parse-error.dts        | 17 +++++++++++++
>  tests/merge-path-ref-missing.dts   | 11 ++++++++
>  tests/merge-path-relative.dts      | 12 +++++++++
>  tests/run_tests.sh                 | 41 ++++++++++++++++++++++++++++++
>  11 files changed, 158 insertions(+)
>  create mode 100644 tests/merge-delete-missing.dts
>  create mode 100644 tests/merge-errors.dts
>  create mode 100644 tests/merge-first-ref-relative.dts
>  create mode 100644 tests/merge-first-ref.dts
>  create mode 100644 tests/merge-labelled-plugin.dts
>  create mode 100644 tests/merge-labelled-relative.dts
>  create mode 100644 tests/merge-omit-missing.dts
>  create mode 100644 tests/merge-parse-error.dts
>  create mode 100644 tests/merge-path-ref-missing.dts
>  create mode 100644 tests/merge-path-relative.dts
> 
> diff --git a/tests/merge-delete-missing.dts b/tests/merge-delete-missing.dts
> new file mode 100644
> index 0000000..b36e39e
> --- /dev/null
> +++ b/tests/merge-delete-missing.dts
> @@ -0,0 +1,9 @@
> +/dts-v1/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* Try to delete a node using a non-existent label */
> +/delete-node/ &nosuchnode;
> diff --git a/tests/merge-errors.dts b/tests/merge-errors.dts
> new file mode 100644
> index 0000000..6d3969f
> --- /dev/null
> +++ b/tests/merge-errors.dts
> @@ -0,0 +1,14 @@
> +/dts-v1/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* Each error is reported, not just the first */

I'm a bit confused by this comment - I only see one error in this
file.

> +&nosuchnode1 {
> +	prop;
> +};
> +
> +/delete-node/ &nosuchnode2;
> +/omit-if-no-ref/ &nosuchnode3;
> diff --git a/tests/merge-first-ref-relative.dts b/tests/merge-first-ref-relative.dts
> new file mode 100644
> index 0000000..103cd1f
> --- /dev/null
> +++ b/tests/merge-first-ref-relative.dts
> @@ -0,0 +1,10 @@
> +/dts-v1/;
> +/plugin/;
> +
> +/* A plugin cannot start with a label-relative reference */

That's slightly misleading, insn't it?  A plugin can't have _any_
label relative references unless they resolve within the plugin.

> +&{label/node} {
> +	prop;
> +};
> +
> +/* Later fragments are still merged */
> +/delete-node/ &nosuchnode;
> diff --git a/tests/merge-first-ref.dts b/tests/merge-first-ref.dts
> new file mode 100644
> index 0000000..8f23a44
> --- /dev/null
> +++ b/tests/merge-first-ref.dts
> @@ -0,0 +1,9 @@
> +/dts-v1/;
> +
> +/* Only a plugin can start with a reference */
> +&nosuchnode {
> +	prop;
> +};
> +
> +/* Later fragments are still merged */
> +/delete-node/ &nosuchnode2;
> diff --git a/tests/merge-labelled-plugin.dts b/tests/merge-labelled-plugin.dts
> new file mode 100644
> index 0000000..3849cc5
> --- /dev/null
> +++ b/tests/merge-labelled-plugin.dts
> @@ -0,0 +1,12 @@
> +/dts-v1/;
> +/plugin/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* Add a label to a node defined in the plugin itself */
> +newlabel: &label {
> +	prop = "value";
> +};
> diff --git a/tests/merge-labelled-relative.dts b/tests/merge-labelled-relative.dts
> new file mode 100644
> index 0000000..00369e3
> --- /dev/null
> +++ b/tests/merge-labelled-relative.dts
> @@ -0,0 +1,14 @@
> +/dts-v1/;
> +/plugin/;
> +
> +/ {
> +	label: node {
> +		subnode {
> +		};
> +	};
> +};
> +
> +/* A plugin cannot use a label-relative reference, even if it is found */
> +newlabel: &{label/subnode} {
> +	prop;
> +};
> diff --git a/tests/merge-omit-missing.dts b/tests/merge-omit-missing.dts
> new file mode 100644
> index 0000000..0357648
> --- /dev/null
> +++ b/tests/merge-omit-missing.dts
> @@ -0,0 +1,9 @@
> +/dts-v1/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* Try to mark a node using a non-existent path */
> +/omit-if-no-ref/ &{/nosuchnode};
> diff --git a/tests/merge-parse-error.dts b/tests/merge-parse-error.dts
> new file mode 100644
> index 0000000..c39318b
> --- /dev/null
> +++ b/tests/merge-parse-error.dts
> @@ -0,0 +1,17 @@
> +/dts-v1/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* Reported even though the parser finds another error below */
> +&nosuchnode {
> +	prop;
> +};
> +
> +/ {
> +	node {
> +		prop = <(1/0)>;
> +	};
> +};
> diff --git a/tests/merge-path-ref-missing.dts b/tests/merge-path-ref-missing.dts
> new file mode 100644
> index 0000000..ad2e35e
> --- /dev/null
> +++ b/tests/merge-path-ref-missing.dts
> @@ -0,0 +1,11 @@
> +/dts-v1/;
> +
> +/ {
> +	node {
> +	};
> +};
> +
> +/* Try to redefine a node using a non-existent path */
> +&{/nosuchnode} {
> +	prop;
> +};
> diff --git a/tests/merge-path-relative.dts b/tests/merge-path-relative.dts
> new file mode 100644
> index 0000000..94a0b38
> --- /dev/null
> +++ b/tests/merge-path-relative.dts
> @@ -0,0 +1,12 @@
> +/dts-v1/;
> +/plugin/;
> +
> +/ {
> +	label: node {
> +	};
> +};
> +
> +/* A plugin cannot use a label-relative path reference */
> +&{label/node} {
> +	prop;
> +};
> diff --git a/tests/run_tests.sh b/tests/run_tests.sh
> index 5bc7817..e0504c0 100755
> --- a/tests/run_tests.sh
> +++ b/tests/run_tests.sh
> @@ -765,6 +765,47 @@ dtc_tests () {
>      run_sh_test "$SRCDIR/dtc-checkfails.sh" phandle_references -- -I dts -O dtb "$SRCDIR/nonexist-node-ref.dts"
>      run_sh_test "$SRCDIR/dtc-checkfails.sh" phandle_references -- -I dts -O dtb "$SRCDIR/nonexist-label-ref.dts"
>      run_sh_test "$SRCDIR/dtc-fatal.sh" -I dts -O dtb "$SRCDIR/nonexist-node-ref2.dts"
> +
> +    # Check each error when merging the fragments of a dts file, with its
> +    # position
> +    run_wrap_error_msg_test "test_label_ref.dts:7.8-15 Label or path handle not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/test_label_ref.dts"
> +    run_wrap_error_msg_test "nonexist-node-ref2.dts:9.1-12 Label or path nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/nonexist-node-ref2.dts"
> +    run_wrap_error_msg_test "merge-first-ref.dts:4.13-6.3 Label or path nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref.dts"
> +    run_wrap_error_msg_test "merge-first-ref.dts:9.15-27 Label or path nosuchnode2 not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref.dts"
> +    run_wrap_error_msg_test "merge-first-ref-relative.dts:5.15-7.3 Label-relative reference label/node not supported in plugin" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref-relative.dts"
> +    run_wrap_error_msg_test "merge-first-ref-relative.dts:10.15-26 Label or path nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-first-ref-relative.dts"
> +    run_wrap_error_msg_test "merge-labelled-relative.dts:12.1-10 Label-relative reference label/subnode not supported in plugin" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-labelled-relative.dts"
> +    run_wrap_error_msg_test "merge-path-relative.dts:10.1-14 Label-relative reference label/node not supported in plugin" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-path-relative.dts"
> +    run_wrap_error_msg_test "merge-path-ref-missing.dts:9.1-15 Label or path /nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-path-ref-missing.dts"
> +    run_wrap_error_msg_test "merge-delete-missing.dts:9.15-26 Label or path nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-delete-missing.dts"
> +    run_wrap_error_msg_test "merge-omit-missing.dts:9.18-32 Label or path /nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-omit-missing.dts"
> +    run_wrap_error_msg_test "merge-errors.dts:9.1-13 Label or path nosuchnode1 not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
> +    run_wrap_error_msg_test "merge-errors.dts:13.15-27 Label or path nosuchnode2 not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
> +    run_wrap_error_msg_test "merge-errors.dts:14.18-30 Label or path nosuchnode3 not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-errors.dts"
> +    run_wrap_error_msg_test "merge-parse-error.dts:9.1-12 Label or path nosuchnode not found" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-parse-error.dts"
> +    run_wrap_error_msg_test "merge-parse-error.dts:15.12-15 Division by zero" \
> +	$DTC -I dts -O dtb -o /dev/null "$SRCDIR/merge-parse-error.dts"
> +
> +    # A labelled reference to a node in a plugin adds the label to it
> +    run_dtc_test -@ -I dts -O dtb -o merge-labelled-plugin.test.dtb \
> +	"$SRCDIR/merge-labelled-plugin.dts"
> +    run_fdtget_test "/node" merge-labelled-plugin.test.dtb /__symbols__ newlabel
> +    run_fdtget_test "value" merge-labelled-plugin.test.dtb /node prop
>      check_tests "$SRCDIR/bad-name-property.dts" name_properties
>  
>      check_tests "$SRCDIR/bad-ncells.dts" address_cells_is_cell size_cells_is_cell interrupts_extended_is_cell
> -- 
> 2.43.0
> 
> 

-- 
David Gibson (he or they)	| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you, not the other way
				| around.
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 02/13] dtc: Add a list for the top-level fragments of a dts file
  2026-11-01 16:36 ` [PATCH 02/13] dtc: Add a list for the top-level fragments of " Simon Glass
@ 2026-11-02  9:49   ` David Gibson
  0 siblings, 0 replies; 19+ messages in thread
From: David Gibson @ 2026-11-02  9:49 UTC (permalink / raw)
  To: Simon Glass; +Cc: Devicetree Compiler

[-- Attachment #1: Type: text/plain, Size: 6542 bytes --]

On Sun, Nov 01, 2026 at 09:36:56AM -0700, Simon Glass wrote:
> A dts file is made up of top-level fragments: the root node, later
> definitions of a node by label or path, and /delete-node/ or
> /omit-if-no-ref/ with a reference. We would like to deal with each
> fragment on its own before it is merged, for example to apply deletes
> consistently or to run some checks on each one, but that requires
> keeping the fragments apart. At present the parser merges each one into
> the tree as soon as it is parsed.

Btw, I'm extremely open to a better name than "fragment" if one occurs
to you.  I believe I originally thought of these as "overlays" but
that's confusing since runtime overlays were invented.

> Add a struct fragment to hold one of these, functions to build one, to
> append it to a list and to free it, and a list of them in struct dt_info
> 
> These are not used yet.
> 
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
>  dtc.h      | 47 ++++++++++++++++++++++++++++++++++++++++++
>  livetree.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 107 insertions(+)
> 
> diff --git a/dtc.h b/dtc.h
> index 473552e..c0d8e8f 100644
> --- a/dtc.h
> +++ b/dtc.h
> @@ -322,12 +322,59 @@ struct reserve_info *chain_reserve_entry(struct reserve_info *first,
>  struct reserve_info *add_reserve_entry(struct reserve_info *list,
>  				       struct reserve_info *new);
>  
> +/*
> + * A fragment is one top-level part of a dts file, after the header and any
> + * memory reservations: the root node, a later definition of a node which
> + * refers to it by label or path, or a /delete-node/ or /omit-if-no-ref/ with
> + * a reference. Fragments are kept apart until they are merged into a single
> + * tree.
> + *
> + * This is not the same as an overlay fragment (a fragment@N node), although
> + * in a plugin a fragment may become one when it is merged.
> + */
> +
> +enum fragment_type {
> +	FRAGMENT_ROOT,		/* / { ... }; */

Why do we need a special type for ROOT, rather than setting ref == "/"?

> +	FRAGMENT_REF,		/* [label:] &ref { ... }; */
> +	FRAGMENT_DELETE_NODE,	/* /delete-node/ &ref; */
> +	FRAGMENT_OMIT_IF_NO_REF, /* /omit-if-no-ref/ &ref; */
> +};
> +
> +/*
> + * Which fields are used depends on the type:
> + *
> + *   FRAGMENT_ROOT            node
> + *   FRAGMENT_REF             ref, path_ref, ref_srcpos and node, plus label
> + *                            and label_srcpos if there is a label
> + *   FRAGMENT_DELETE_NODE     ref, path_ref and ref_srcpos
> + *   FRAGMENT_OMIT_IF_NO_REF  ref, path_ref and ref_srcpos
> + *
> + * Unused fields are NULL or false.
> + */
> +struct fragment {
> +	enum fragment_type type;
> +	char *ref;			/* label or path referred to */
> +	bool path_ref;			/* ref is a path, i.e. &{...} */

This can be derived from ref, so I don't think we need to store it.

> +	struct srcpos *ref_srcpos;	/* position of ref */
> +	char *label;			/* label to add to the target */
> +	struct srcpos *label_srcpos;	/* position of label */
> +	struct node *node;		/* the node definition */
> +
> +	struct fragment *next;		/* NULL at the end of the list */
> +};
> +
> +struct fragment *build_fragment(enum fragment_type type, struct node *node);
> +struct fragment *build_fragment_ref(char *ref, bool path_ref,
> +				    struct srcpos *srcpos);
> +struct fragment *append_fragment(struct fragment *list, struct fragment *new);
> +void free_fragment(struct fragment *f);
>  
>  struct dt_info {
>  	unsigned int dtsflags;
>  	struct reserve_info *reservelist;
>  	uint32_t boot_cpuid_phys;
>  	struct node *dt;		/* the device tree */
> +	struct fragment *fragments;	/* not yet merged into dt */
>  	const char *outname;		/* filename being written to, "-" for stdout */
>  };
>  
> diff --git a/livetree.c b/livetree.c
> index abe73de..982f0a6 100644
> --- a/livetree.c
> +++ b/livetree.c
> @@ -448,6 +448,65 @@ struct reserve_info *add_reserve_entry(struct reserve_info *list,
>  	return list;
>  }
>  
> +/* Create a fragment of the given type, holding an optional node */
> +struct fragment *build_fragment(enum fragment_type type, struct node *node)

Since there's no ref provided, presumably this must of be
FRAGMENT_ROOT, so why a type parameter?

> +{
> +	struct fragment *new = xmalloc(sizeof(*new));
> +
> +	memset(new, '\0', sizeof(*new));
> +
> +	new->type = type;
> +	new->node = node;
> +
> +	return new;
> +}
> +
> +/*
> + * Create a fragment which refers to a label, or to a path if path_ref. Its
> + * node is NULL, so the caller must either set the node or change the type to
> + * FRAGMENT_DELETE_NODE or FRAGMENT_OMIT_IF_NO_REF

That seems a clumsy interface.  Maybe different constructor functions
for the different types, that take all the necessary pieces?

> + */
> +struct fragment *build_fragment_ref(char *ref, bool path_ref,
> +				    struct srcpos *srcpos)

Here too, path_ref should be derivable from ref.

> +{
> +	struct fragment *new = build_fragment(FRAGMENT_REF, NULL);
> +
> +	new->ref = ref;
> +	new->path_ref = path_ref;
> +	new->ref_srcpos = srcpos_copy(srcpos);
> +
> +	return new;
> +}
> +
> +/* Add a fragment to the end of a list, returning the new head */
> +struct fragment *append_fragment(struct fragment *list, struct fragment *new)
> +{
> +	struct fragment *last;
> +
> +	new->next = NULL;
> +
> +	if (!list)
> +		return new;
> +
> +	for (last = list; last->next; last = last->next)
> +		;
> +
> +	last->next = new;
> +
> +	return list;
> +}
> +
> +/*
> + * Free a fragment, but not its node, reference or label, since the tree may
> + * still use these
> + */
> +void free_fragment(struct fragment *f)
> +{
> +	srcpos_free(f->ref_srcpos);
> +	srcpos_free(f->label_srcpos);
> +	free(f);
> +}
> +
>  static void fill_fullpaths(struct node *tree, const char *prefix)
>  {
>  	struct node *child;
> @@ -475,6 +534,7 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
>  	dti->dtsflags = dtsflags;
>  	dti->reservelist = reservelist;
>  	dti->dt = tree;
> +	dti->fragments = NULL;
>  	dti->boot_cpuid_phys = boot_cpuid_phys;
>  
>  	fill_fullpaths(dti->dt, "");
> -- 
> 2.43.0
> 
> 

-- 
David Gibson (he or they)	| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you, not the other way
				| around.
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar
  2026-11-01 16:36 ` [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar Simon Glass
@ 2026-11-02  9:55   ` David Gibson
  0 siblings, 0 replies; 19+ messages in thread
From: David Gibson @ 2026-11-02  9:55 UTC (permalink / raw)
  To: Simon Glass; +Cc: Devicetree Compiler

[-- Attachment #1: Type: text/plain, Size: 4032 bytes --]

On Sun, Nov 01, 2026 at 09:36:57AM -0700, Simon Glass wrote:
> The grammar actions merge each top-level fragment of a dts file into the
> tree as it is parsed, so the merge logic is spread over the rules for
> each kind of fragment. This makes it hard to follow and to change.
> 
> As a first step towards moving this logic into livetree.c, move the
> action for a top-level /delete-node/ into a new merge_delete_node(),
> passing it a struct fragment for the reference. Add a fragment_ref rule
> to build this.
> 
> The output does not change.
> 
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
>  dtc-parser.y | 26 +++++++++++++++++---------
>  dtc.h        |  1 +
>  livetree.c   | 19 +++++++++++++++++++
>  3 files changed, 37 insertions(+), 9 deletions(-)
> 
> diff --git a/dtc-parser.y b/dtc-parser.y
> index e47f80d..61301a6 100644
> --- a/dtc-parser.y
> +++ b/dtc-parser.y
> @@ -47,6 +47,7 @@ static bool is_ref_relative(const char *ref)
>  	struct node *node;
>  	struct node *nodelist;
>  	struct reserve_info *re;
> +	struct fragment *fragment;
>  	uint64_t integer;
>  	unsigned int flags;
>  }
> @@ -80,6 +81,7 @@ static bool is_ref_relative(const char *ref)
>  %type <prop> propdef
>  %type <proplist> proplist
>  %type <labelref> dt_ref
> +%type <fragment> fragment_ref
>  
>  %type <node> devicetree
>  %type <node> nodedef
> @@ -157,6 +159,17 @@ memreserve:
>  
>  dt_ref: DT_LABEL_REF | DT_PATH_REF;
>  
> +fragment_ref:
> +	  DT_LABEL_REF
> +		{
> +			$$ = build_fragment_ref($1, false, &@$);
> +		}
> +	| DT_PATH_REF
> +		{
> +			$$ = build_fragment_ref($1, true, &@$);
> +		}
> +	;
> +

This seems weird.  If the point is to allow either kind of ref, we
already have the dt_ref nonterminal for that.  If the point is to
build fragments, rather than just refs, then why do it here before you
have the information you need to properly build the fragment?

>  devicetree:
>  	  '/' nodedef
>  		{
> @@ -236,16 +249,11 @@ devicetree:
>  			}
>  			$$ = $1;
>  		}
> -	| devicetree DT_DEL_NODE dt_ref ';'
> +	| devicetree DT_DEL_NODE fragment_ref ';'
>  		{
> -			struct node *target = get_node_by_ref($1, $3);
> -
> -			if (target)
> -				delete_node(target);
> -			else
> -				ERROR(&@3, "Label or path %s not found", $3);
> -
> -
> +			$3->type = FRAGMENT_DELETE_NODE;
> +			if (merge_delete_node($1, $3))
> +				treesource_error = true;
>  			$$ = $1;
>  		}
>  	| devicetree DT_OMIT_NO_REF dt_ref ';'
> diff --git a/dtc.h b/dtc.h
> index c0d8e8f..9f9076d 100644
> --- a/dtc.h
> +++ b/dtc.h
> @@ -368,6 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
>  				    struct srcpos *srcpos);
>  struct fragment *append_fragment(struct fragment *list, struct fragment *new);
>  void free_fragment(struct fragment *f);
> +bool merge_delete_node(struct node *dt, struct fragment *f);
>  
>  struct dt_info {
>  	unsigned int dtsflags;
> diff --git a/livetree.c b/livetree.c
> index 982f0a6..5c9ec48 100644
> --- a/livetree.c
> +++ b/livetree.c
> @@ -542,6 +542,25 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
>  	return dti;
>  }
>  
> +/*
> + * Delete the node which a /delete-node/ fragment refers to
> + *
> + * Returns true if there is an error, which has been reported.
> + */
> +bool merge_delete_node(struct node *dt, struct fragment *f)
> +{
> +	struct node *target = get_node_by_ref(dt, f->ref);

assert() that f->type == DELETE_NODE, maybe?

> +	if (!target) {
> +		srcpos_error(f->ref_srcpos, "Error",
> +			     "Label or path %s not found", f->ref);
> +		return true;
> +	}
> +	delete_node(target);
> +
> +	return false;
> +}
> +
>  /*
>   * Tree accessor functions
>   */
> -- 
> 2.43.0
> 
> 

-- 
David Gibson (he or they)	| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you, not the other way
				| around.
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 04/13] dtc: Move the /omit-if-no-ref/ handling out of the grammar
  2026-11-01 16:36 ` [PATCH 04/13] dtc: Move the /omit-if-no-ref/ " Simon Glass
@ 2026-11-02 10:03   ` David Gibson
  0 siblings, 0 replies; 19+ messages in thread
From: David Gibson @ 2026-11-02 10:03 UTC (permalink / raw)
  To: Simon Glass; +Cc: Devicetree Compiler

[-- Attachment #1: Type: text/plain, Size: 2837 bytes --]

On Sun, Nov 01, 2026 at 09:36:58AM -0700, Simon Glass wrote:
> The grammar action for a top-level /omit-if-no-ref/ still holds its
> merge logic.
> 
> Move it into a new merge_omit_if_no_ref(), passing it a struct fragment
> for the reference.
> 
> The output does not change.
> 
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
>  dtc-parser.y | 13 ++++---------
>  dtc.h        |  1 +
>  livetree.c   | 20 ++++++++++++++++++++
>  3 files changed, 25 insertions(+), 9 deletions(-)
> 
> diff --git a/dtc-parser.y b/dtc-parser.y
> index 61301a6..089ab55 100644
> --- a/dtc-parser.y
> +++ b/dtc-parser.y
> @@ -256,16 +256,11 @@ devicetree:
>  				treesource_error = true;
>  			$$ = $1;
>  		}
> -	| devicetree DT_OMIT_NO_REF dt_ref ';'
> +	| devicetree DT_OMIT_NO_REF fragment_ref ';'

As in the previous patch, the fragment_ref nonterminal doesn't make
much sense to me.

>  		{
> -			struct node *target = get_node_by_ref($1, $3);
> -
> -			if (target)
> -				omit_node_if_unused(target);
> -			else
> -				ERROR(&@3, "Label or path %s not found", $3);
> -
> -
> +			$3->type = FRAGMENT_OMIT_IF_NO_REF;
> +			if (merge_omit_if_no_ref($1, $3))
> +				treesource_error = true;
>  			$$ = $1;
>  		}
>  	;
> diff --git a/dtc.h b/dtc.h
> index 9f9076d..e747dc0 100644
> --- a/dtc.h
> +++ b/dtc.h
> @@ -369,6 +369,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
>  struct fragment *append_fragment(struct fragment *list, struct fragment *new);
>  void free_fragment(struct fragment *f);
>  bool merge_delete_node(struct node *dt, struct fragment *f);
> +bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
>  
>  struct dt_info {
>  	unsigned int dtsflags;
> diff --git a/livetree.c b/livetree.c
> index 5c9ec48..5ed2085 100644
> --- a/livetree.c
> +++ b/livetree.c
> @@ -561,6 +561,26 @@ bool merge_delete_node(struct node *dt, struct fragment *f)
>  	return false;
>  }
>  
> +/*
> + * Mark the node which an /omit-if-no-ref/ fragment refers to, so that it is
> + * dropped if nothing refers to it
> + *
> + * Returns true if there is an error, which has been reported.
> + */
> +bool merge_omit_if_no_ref(struct node *dt, struct fragment *f)
> +{
> +	struct node *target = get_node_by_ref(dt, f->ref);
> +
> +	if (!target) {
> +		srcpos_error(f->ref_srcpos, "Error",
> +			     "Label or path %s not found", f->ref);
> +		return true;
> +	}
> +	omit_node_if_unused(target);
> +
> +	return false;
> +}
> +
>  /*
>   * Tree accessor functions
>   */
> -- 
> 2.43.0
> 
> 

-- 
David Gibson (he or they)	| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you, not the other way
				| around.
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c
  2026-11-01 16:36 ` [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c Simon Glass
@ 2026-11-02 10:04   ` David Gibson
  0 siblings, 0 replies; 19+ messages in thread
From: David Gibson @ 2026-11-02 10:04 UTC (permalink / raw)
  To: Simon Glass; +Cc: Devicetree Compiler

[-- Attachment #1: Type: text/plain, Size: 2245 bytes --]

On Sun, Nov 01, 2026 at 09:36:59AM -0700, Simon Glass wrote:
61;8401;1c> The remaining grammar actions which merge a fragment use
> is_ref_relative() to reject a label-relative reference in a plugin.
> 
> Move it to livetree.c, so that it is available when these actions move
> there too, and add a comment.
> 
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>

Reviewed-by: David Gibson <david@gibson.dropbear.id.au>

> ---
> 
>  dtc-parser.y | 5 -----
>  dtc.h        | 1 +
>  livetree.c   | 6 ++++++
>  3 files changed, 7 insertions(+), 5 deletions(-)
> 
> diff --git a/dtc-parser.y b/dtc-parser.y
> index 089ab55..7545895 100644
> --- a/dtc-parser.y
> +++ b/dtc-parser.y
> @@ -24,11 +24,6 @@ extern void yyerror(char const *s);
>  extern struct dt_info *parser_output;
>  extern bool treesource_error;
>  
> -static bool is_ref_relative(const char *ref)
> -{
> -	return ref[0] != '/' && strchr(&ref[1], '/');
> -}
> -
>  %}
>  
>  %union {
> diff --git a/dtc.h b/dtc.h
> index e747dc0..d145b03 100644
> --- a/dtc.h
> +++ b/dtc.h
> @@ -368,6 +368,7 @@ struct fragment *build_fragment_ref(char *ref, bool path_ref,
>  				    struct srcpos *srcpos);
>  struct fragment *append_fragment(struct fragment *list, struct fragment *new);
>  void free_fragment(struct fragment *f);
> +bool is_ref_relative(const char *ref);
>  bool merge_delete_node(struct node *dt, struct fragment *f);
>  bool merge_omit_if_no_ref(struct node *dt, struct fragment *f);
>  
> diff --git a/livetree.c b/livetree.c
> index 5ed2085..027682a 100644
> --- a/livetree.c
> +++ b/livetree.c
> @@ -542,6 +542,12 @@ struct dt_info *build_dt_info(unsigned int dtsflags,
>  	return dti;
>  }
>  
> +/* Check whether a reference is relative to a label, e.g. &{label/node} */
> +bool is_ref_relative(const char *ref)
> +{
> +	return ref[0] != '/' && strchr(&ref[1], '/');
> +}
> +
>  /*
>   * Delete the node which a /delete-node/ fragment refers to
>   *
> -- 
> 2.43.0
> 
> 

-- 
David Gibson (he or they)	| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you, not the other way
				| around.
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

end of thread, other threads:[~2026-11-02 10:04 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-01 16:36 [PATCH 00/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
2026-11-01 16:36 ` [PATCH 01/13] tests: Check each error when merging a dts file Simon Glass
2026-11-02  6:32   ` David Gibson
2026-11-01 16:36 ` [PATCH 02/13] dtc: Add a list for the top-level fragments of " Simon Glass
2026-11-02  9:49   ` David Gibson
2026-11-01 16:36 ` [PATCH 03/13] dtc: Move the /delete-node/ handling out of the grammar Simon Glass
2026-11-02  9:55   ` David Gibson
2026-11-01 16:36 ` [PATCH 04/13] dtc: Move the /omit-if-no-ref/ " Simon Glass
2026-11-02 10:03   ` David Gibson
2026-11-01 16:36 ` [PATCH 05/13] dtc: Move is_ref_relative() to livetree.c Simon Glass
2026-11-02 10:04   ` David Gibson
2026-11-01 16:37 ` [PATCH 06/13] dtc: Move label-reference handling out of the grammar Simon Glass
2026-11-01 16:37 ` [PATCH 07/13] dtc: Move path-reference " Simon Glass
2026-11-01 16:37 ` [PATCH 08/13] dtc: Move labelled-reference " Simon Glass
2026-11-01 16:37 ` [PATCH 09/13] dtc: Move first-reference " Simon Glass
2026-11-01 16:37 ` [PATCH 10/13] dtc: Add merge_fragment() to merge any fragment Simon Glass
2026-11-01 16:37 ` [PATCH 11/13] dtc: Add helpers for the errors when merging a fragment Simon Glass
2026-11-01 16:37 ` [PATCH 12/13] dtc: Merge the fragments of a dts file after parsing Simon Glass
2026-11-01 16:37 ` [PATCH 13/13] dtc: Merge the fragments of a dts file in main() Simon Glass

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