From: David Gibson <david@gibson.dropbear.id.au>
To: Simon Glass <sjg@chromium.org>
Cc: Devicetree Compiler <devicetree-compiler@vger.kernel.org>
Subject: Re: [PATCH 1/2] dtc: Apply deletes in the section which has them
Date: Wed, 28 Oct 2026 14:47:15 +1100 [thread overview]
Message-ID: <auFwL0wwGhzdmc-J@gractus.seuss> (raw)
In-Reply-To: <20261026173529.3080952-2-sjg@chromium.org>
[-- Attachment #1: Type: text/plain, Size: 14331 bytes --]
On Mon, Oct 26, 2026 at 11:35:19AM -0600, Simon Glass wrote:
> The parser holds each /delete-property/ and /delete-node/ as a
> placeholder entry, flagged as deleted, among the node's properties or
> children. It only takes effect when merge_nodes() merges its section
> into an earlier one. In the first section of a dts it is ignored, so
> 'a; /delete-property/ a;' leaves 'a' in the tree. The same happens in
> any node which a later section adds, since merge_nodes() attaches a new
> child as it is. An ignored /delete-node/ also leaves its placeholder in
> the tree, which then shows up as '(null): Duplicate node name'
>
> Apply each delete when its node is built, by dropping the earlier
> entries of the same name from that node. Nothing can refer to the node
> yet, so they can be unlinked rather than flagged as deleted. Keep the
> placeholder, so that merging the node into an earlier definition still
> deletes there too.
>
> When a node joins the tree without being merged, as the first section,
> as a new child in merge_nodes() or as an overlay fragment, drop its
> placeholders, since there is nothing earlier for them to delete. This
> keeps placeholders and entries deleted within a section out of the tree,
> so that nothing which walks the tree has to skip them.
>
> Duplicate entries within a section are still reported as errors, since
> nothing is merged.
>
> Closes: https://github.com/dgibson/dtc/issues/37
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Simon Glass <sjg@chromium.org>
I think this is an improvement over the current situation, hence,
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
However, there are some semantics that could do with tweaking further
in future.
> diff --git a/dtc-parser.y b/dtc-parser.y
> index e47f80d..0282f7b 100644
> --- a/dtc-parser.y
> +++ b/dtc-parser.y
> @@ -160,6 +160,7 @@ dt_ref: DT_LABEL_REF | DT_PATH_REF;
> devicetree:
> '/' nodedef
> {
> + drop_delete_placeholders($2);
> $$ = name_node($2, "");
> }
> | devicetree '/' nodedef
> diff --git a/dtc.h b/dtc.h
> index 473552e..3e5109d 100644
> --- a/dtc.h
> +++ b/dtc.h
> @@ -277,6 +277,7 @@ struct node *omit_node_if_unused(struct node *node);
> struct node *reference_node(struct node *node);
> struct node *chain_node(struct node *first, struct node *list);
> struct node *merge_nodes(struct node *old_node, struct node *new_node);
> +void drop_delete_placeholders(struct node *node);
> struct node *add_orphan_node(struct node *old_node, struct node *new_node, char *ref);
>
> void add_property(struct node *node, struct property *prop);
> diff --git a/livetree.c b/livetree.c
> index abe73de..b474a05 100644
> --- a/livetree.c
> +++ b/livetree.c
> @@ -85,6 +85,68 @@ struct property *reverse_properties(struct property *first)
> return head;
> }
>
> +/*
> + * The parser holds each /delete-property/ and /delete-node/ as a placeholder
> + * entry, flagged as deleted, among the node's properties or children. Apply
> + * each one by dropping the earlier entries of the same name in the node. The
> + * placeholders stay, so that merging the node into an earlier definition
> + * deletes there too
> + */
> +static void apply_deletes(struct node *node)
> +{
> + struct property *prop, **pp;
> + struct node *child, **np;
> +
> + for (prop = node->proplist; prop; prop = prop->next) {
> + if (!prop->deleted)
> + continue;
> + for (pp = &node->proplist; *pp != prop;) {
> + if (streq((*pp)->name, prop->name))
> + *pp = (*pp)->next;
> + else
> + pp = &(*pp)->next;
> + }
This leaks, but that's more or less accepted practice in dtc.
> + }
> +
> + for (child = node->children; child; child = child->next_sibling) {
> + if (!child->deleted)
> + continue;
> + for (np = &node->children; *np != child;) {
> + if (streq((*np)->name, child->name))
> + *np = (*np)->next_sibling;
> + else
> + np = &(*np)->next_sibling;
> + }
> + }
> +}
> +
> +/*
> + * Drop the /delete-property/ and /delete-node/ placeholders from a subtree
> + * which joins the tree without being merged, since there is nothing earlier
I find this description a bit confusing. I think the salient point is
that this is called on a subtree that's the first in the list of
things to be merged.
> + * for them to delete
> + */
> +void drop_delete_placeholders(struct node *node)
> +{
> + struct property **pp = &node->proplist;
> + struct node **np = &node->children;
> +
> + while (*pp) {
> + if ((*pp)->deleted)
> + *pp = (*pp)->next;
> + else
> + pp = &(*pp)->next;
> + }
> +
> + while (*np) {
> + if ((*np)->deleted) {
> + *np = (*np)->next_sibling;
> + } else {
> + drop_delete_placeholders(*np);
> + np = &(*np)->next_sibling;
> + }
> + }
> +}
> +
> struct node *build_node(struct property *proplist, struct node *children,
> struct srcpos *srcpos)
> {
> @@ -100,6 +162,7 @@ struct node *build_node(struct property *proplist, struct node *children,
> for_each_child(new, child) {
> child->parent = new;
> }
> + apply_deletes(new);
>
> return new;
> }
> @@ -212,8 +275,10 @@ struct node *merge_nodes(struct node *old_node, struct node *new_node)
> }
>
> /* if no collision occurred, add child to the old node. */
> - if (new_child)
> + if (new_child) {
> + drop_delete_placeholders(new_child);
I think this is only correct if merges are always applied left to
right. I think that's the case, but it's not terribly obvious right
here. It effectively makes the merge operation non-associative, which
might be confusing.
> add_child(old_node, new_child);
> + }
> }
>
> old_node->srcpos = srcpos_extend(old_node->srcpos, new_node->srcpos);
> @@ -245,6 +310,8 @@ struct node * add_orphan_node(struct node *dt, struct node *new_node, char *ref)
> p = build_property("target", d, NULL);
> }
>
> + drop_delete_placeholders(new_node);
> +
Dropping placeholders here is semantically consistent, but possibly
risky. Accepting a fragment with /delete/s without error might give
the impression that those deletes will take effect when the overlay is
applied, but that's not the case (runtime overlays have no mechanism
for encoding deletes).
I think it would be preferable to give an error if a fragment has any
unapplied delete markers.
> xasprintf(&name, "fragment@%u",
> next_orphan_fragment++);
> name_node(new_node, "__overlay__");
> diff --git a/tests/delete_in_section.dts b/tests/delete_in_section.dts
> new file mode 100644
> index 0000000..e742e79
> --- /dev/null
> +++ b/tests/delete_in_section.dts
> @@ -0,0 +1,125 @@
> +/dts-v1/;
> +
> +/*
> + * Delete properties and nodes defined earlier in the same section. This is
> + * the first section, so nothing is merged into an earlier one
> + */
> +
> +/ {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + gone-prop;
> + /delete-property/ gone-prop;
> + kept-prop;
> + again-prop = "old";
> + /delete-property/ again-prop;
> + again-prop = "new";
> + both-prop;
> +
> + /* Deleted and added again here, then changed by a later section */
> + redel-prop = "old";
> + /delete-property/ redel-prop;
> + redel-prop = "new";
> + reset-prop = "old";
> + /delete-property/ reset-prop;
> + reset-prop = "new";
> +
> + gone-node {
> + };
> + /delete-node/ gone-node;
> +
> + again-node {
> + old;
> + };
> + /delete-node/ again-node;
> + again-node {
> + new;
> + };
> +
> + lbl: node {
> + inner-prop;
> + /delete-property/ inner-prop;
> +
> + inner-node {
> + };
> + /delete-node/ inner-node;
> + };
> +
> + redel-node {
> + old;
> + };
> + /delete-node/ redel-node;
> + redel-node {
> + new;
> + };
> +
> + remerge-node {
> + old;
> + };
> + /delete-node/ remerge-node;
> + remerge-node {
> + new;
> + };
> +
> + /* Checks must not see what is deleted here */
> + named-node {
> + name = "other";
> + /delete-property/ name;
> + };
> +
> + bus {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + child@1 {
> + reg = <1>;
> + };
> + /delete-node/ child@1;
> + };
> +
> + /* Deleted here, then added again by a later section */
> + readd@1 {
> + reg = <1>;
> + old;
> + };
> + /delete-node/ readd@1;
> + /delete-node/ placeholder@2;
I feel like not throwing an error on deletes of things that don't
exist is a footgun.
> +};
> +
> +/* Delete within nodes which a later section adds, and within the merge */
> +
> +/ {
> + both-prop = "new";
> + /delete-property/ both-prop;
> + /delete-property/ redel-prop;
> + reset-prop = "later";
> +
> + new-node {
> + new-prop;
> + /delete-property/ new-prop;
> + kept;
> + };
> +
> + /delete-node/ redel-node;
> +
> + remerge-node {
> + later;
> + };
> +
> + readd@1 {
> + reg = <1>;
> + later;
> + };
> +
> + placeholder@2 {
> + reg = <2>;
> + };
> +};
> +
> +&lbl {
> + child {
> + child-prop;
> + /delete-property/ child-prop;
> + };
> +};
> diff --git a/tests/delete_in_section_badref.dts b/tests/delete_in_section_badref.dts
> new file mode 100644
> index 0000000..79a40e4
> --- /dev/null
> +++ b/tests/delete_in_section_badref.dts
> @@ -0,0 +1,11 @@
> +/dts-v1/;
> +
> +/* A reference to a node deleted in the same section has nothing to refer to */
> +
> +/ {
> + ref = <&gone>;
> +
> + gone: node {
> + };
> + /delete-node/ node;
> +};
> diff --git a/tests/delete_in_section_labels.dts b/tests/delete_in_section_labels.dts
> new file mode 100644
> index 0000000..fbde838
> --- /dev/null
> +++ b/tests/delete_in_section_labels.dts
> @@ -0,0 +1,28 @@
> +/dts-v1/;
> +
> +/*
> + * A label moves to the new definition of a node deleted in the same
> + * section, and labels on deleted nodes stay out of __symbols__
> + */
> +
> +/ {
> + ref = <&moved>;
> +
> + moved: node {
> + old;
> + };
> + /delete-node/ node;
> + moved: node {
> + new;
> + };
> +
> + gone: gone-node {
> + };
> + /delete-node/ gone-node;
> +};
> +
> +/ {
> + later: later-node {
> + };
> + /delete-node/ later-node;
> +};
> diff --git a/tests/delete_in_section_labels_ref.dts b/tests/delete_in_section_labels_ref.dts
> new file mode 100644
> index 0000000..5d8cf38
> --- /dev/null
> +++ b/tests/delete_in_section_labels_ref.dts
> @@ -0,0 +1,9 @@
> +/dts-v1/;
> +
> +/ {
> + ref = <&moved>;
> +
> + moved: node {
> + new;
> + };
> +};
> diff --git a/tests/delete_in_section_overlay.dts b/tests/delete_in_section_overlay.dts
> new file mode 100644
> index 0000000..e3e8f71
> --- /dev/null
> +++ b/tests/delete_in_section_overlay.dts
> @@ -0,0 +1,21 @@
> +/dts-v1/;
> +/plugin/;
> +
> +/* Delete within the fragments of an overlay */
> +
> +&target {
> + a;
> + /delete-property/ a;
> + b;
> + /delete-property/ c;
> +
> + gone {
> + };
> + /delete-node/ gone;
> + /delete-node/ d;
> +};
> +
> +&{/path} {
> + p;
> + /delete-property/ p;
> +};
> diff --git a/tests/delete_in_section_overlay_ref.dts b/tests/delete_in_section_overlay_ref.dts
> new file mode 100644
> index 0000000..48f36b3
> --- /dev/null
> +++ b/tests/delete_in_section_overlay_ref.dts
> @@ -0,0 +1,9 @@
> +/dts-v1/;
> +/plugin/;
> +
> +&target {
> + b;
> +};
> +
> +&{/path} {
> +};
> diff --git a/tests/delete_in_section_ref.dts b/tests/delete_in_section_ref.dts
> new file mode 100644
> index 0000000..20d459a
> --- /dev/null
> +++ b/tests/delete_in_section_ref.dts
> @@ -0,0 +1,44 @@
> +/dts-v1/;
> +
> +/ {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + kept-prop;
> + again-prop = "new";
> + reset-prop = "later";
> +
> + again-node {
> + new;
> + };
> +
> + lbl: node {
> + child {
> + };
> + };
> +
> + remerge-node {
> + new;
> + later;
> + };
> +
> + named-node {
> + };
> +
> + bus {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
> +
> + new-node {
> + kept;
> + };
> +
> + readd@1 {
> + reg = <1>;
> + later;
> + };
> +
> + placeholder@2 {
> + reg = <2>;
> + };
> +};
> diff --git a/tests/run_tests.sh b/tests/run_tests.sh
> index 842b543..2650df6 100755
> --- a/tests/run_tests.sh
> +++ b/tests/run_tests.sh
> @@ -725,6 +725,27 @@ dtc_tests () {
> run_dtc_test -I dts -O dts -o delete_reinstate_multilabel.dts.test.dts "$SRCDIR/delete_reinstate_multilabel.dts"
> run_wrap_test cmp delete_reinstate_multilabel.dts.test.dts "$SRCDIR/delete_reinstate_multilabel_ref.dts"
>
> + # Check deletes within the section which has them
> + run_dtc_test -E reg_format -E avoid_unnecessary_addr_size -I dts -O dtb \
> + -o delete_in_section.test.dtb "$SRCDIR/delete_in_section.dts"
> + run_dtc_test -I dts -O dtb -o delete_in_section_ref.test.dtb \
> + "$SRCDIR/delete_in_section_ref.dts"
> + run_test dtbs_equal_ordered delete_in_section.test.dtb delete_in_section_ref.test.dtb
> + run_dtc_test -@ -I dts -O dtb -o delete_in_section_labels.test.dtb \
> + "$SRCDIR/delete_in_section_labels.dts"
> + run_dtc_test -@ -I dts -O dtb -o delete_in_section_labels_ref.test.dtb \
> + "$SRCDIR/delete_in_section_labels_ref.dts"
> + run_test dtbs_equal_ordered delete_in_section_labels.test.dtb \
> + delete_in_section_labels_ref.test.dtb
> + run_dtc_test -I dts -O dtb -o delete_in_section_overlay.test.dtb \
> + "$SRCDIR/delete_in_section_overlay.dts"
> + run_dtc_test -I dts -O dtb -o delete_in_section_overlay_ref.test.dtb \
> + "$SRCDIR/delete_in_section_overlay_ref.dts"
> + run_test dtbs_equal_ordered delete_in_section_overlay.test.dtb \
> + delete_in_section_overlay_ref.test.dtb
> + run_sh_test "$SRCDIR/dtc-checkfails.sh" phandle_references \
> + -n duplicate_node_names -- -I dts -O dtb "$SRCDIR/delete_in_section_badref.dts"
> +
> # Check some checks
> check_tests "$SRCDIR/dup-nodename.dts" duplicate_node_names
> check_tests "$SRCDIR/dup-propname.dts" duplicate_property_names
> --
> 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 --]
next prev parent reply other threads:[~2026-10-28 3:51 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 17:35 [PATCH 0/2] dtc: Fix deletes within a section and before a redefinition Simon Glass
2026-10-26 17:35 ` [PATCH 1/2] dtc: Apply deletes in the section which has them Simon Glass
2026-10-27 7:41 ` Herve Codina
2026-10-28 3:47 ` David Gibson [this message]
2026-10-26 17:35 ` [PATCH 2/2] dtc: Remove a deleted node or property from the tree Simon Glass
2026-10-27 7:45 ` Herve Codina
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=auFwL0wwGhzdmc-J@gractus.seuss \
--to=david@gibson.dropbear.id.au \
--cc=devicetree-compiler@vger.kernel.org \
--cc=sjg@chromium.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox