All of lore.kernel.org
 help / color / mirror / Atom feed
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 --]

  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 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.