Devicetree
 help / color / mirror / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: Herve Codina <herve.codina@bootlin.com>
Cc: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
	David Lechner <dlechner@baylibre.com>,
	Ayush Singh <ayush@beagleboard.org>,
	Geert Uytterhoeven <geert@linux-m68k.org>,
	devicetree-compiler@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree-spec@vger.kernel.org,
	Hui Pu <hui.pu@gehealthcare.com>,
	Ian Ray <ian.ray@gehealthcare.com>,
	Luca Ceresoli <luca.ceresoli@bootlin.com>,
	Thomas Petazzoni <thomas.petazzoni@bootlin.com>,
	Frank Li <Frank.Li@nxp.com>
Subject: Re: [PATCH v3 02/15] libfdt: Don't assume the root node is available at offset 0
Date: Sun, 30 Aug 2026 13:21:06 +1000	[thread overview]
Message-ID: <apOhk-NQusFgV5Sx@gractus.seuss> (raw)
In-Reply-To: <20260826083146.304291-3-herve.codina@bootlin.com>

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

On Wed, Aug 26, 2026 at 10:31:33AM +0200, Herve Codina wrote:
> In several places, libfdt assumes that the root offset (i.e. the first
> FDT_BEGIN_NODE tag) is present at the offset 0 of the structure block.
> 
> This assumption is not correct. A FDT_NOP can be present at the offset 0
> and this is a legit case. Indeed, the device-tree specification [0]
> defines the FDT_NOP tag as follow:
> 
>    The FDT_NOP token will be ignored by any program parsing the device
>    tree. This token has no extra data; so it is followed immediately by
>    the next token, which can be any valid token. A property or node
>    definition in the tree can be overwritten with FDT_NOP tokens to
>    remove it from the tree without needing to move other sections of
>    the tree’s representation in the devicetree blob.
> 
> Nothing refers to any location for this tag and it has to be simply
> ignored. Having this tag at offset 0 doesn't make an exception, the tag
> has to be ignored.
> 
> Introduce fdt_root_offset() in order to get the offset of the root
> node (first FDT_BEGIN_NODE tag) available in a fdt blob taking care of
> FDT_NOP tags.
> 
> Use this function to get the root node offset instead of looking for
> this node at offset 0.
> 
> [0] https://github.com/devicetree-org/devicetree-specification/blob/main/source/chapter5-flattened-format.rst?plain=1#L317


You convinced me of the necessity of this the last time around, but I
think the implementation could be improbved.

> Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> ---
>  libfdt/fdt.c       | 39 +++++++++++++++++++++++++++++--
>  libfdt/fdt_ro.c    | 57 ++++++++++++++++++++++++++++++++++++++--------
>  libfdt/fdt_rw.c    | 12 ++++++++++
>  libfdt/libfdt.h    | 15 +++++++++++-
>  libfdt/version.lds |  1 +
>  5 files changed, 111 insertions(+), 13 deletions(-)
> 
> diff --git a/libfdt/fdt.c b/libfdt/fdt.c
> index 56d4dcb2..eb803e8a 100644
> --- a/libfdt/fdt.c
> +++ b/libfdt/fdt.c
> @@ -252,13 +252,48 @@ int fdt_check_prop_offset_(const void *fdt, int offset)
>  	return offset;
>  }
>  
> -int fdt_next_node(const void *fdt, int offset, int *depth)
> +int fdt_root_offset(const void *fdt)
>  {
>  	int nextoffset = 0;
> +	int offset;
> +	uint32_t tag;
> +
> +	do {
> +		offset = nextoffset;
> +		tag = fdt_next_tag(fdt, offset, &nextoffset);
> +		switch (tag) {
> +		case FDT_END_NODE:
> +		case FDT_PROP:
> +			return -FDT_ERR_BADSTRUCTURE;
> +
> +		case FDT_BEGIN_NODE:
> +			return offset;
> +
> +		default:
> +			break;
> +		}
> +	} while (tag != FDT_END);
> +
> +	return (nextoffset < 0) ? nextoffset : -FDT_ERR_NOTFOUND;

This should be BADSTRUCTURE rather than NOTFOUND: a dtb without a root
node is not validly constructed.  (This could matter quite a lot if
this error gets propagated up a call chain - a NOTFOUND is usually
non-fatal, but BADSTRUCTURE means there's basically nothing that can
usefully be done with the dtb, which the caller needs to know as soon
as possible).

> +}
> +
> +int fdt_next_node(const void *fdt, int offset, int *depth)
> +{
> +	int nextoffset = offset;

This initialiser should be omitted, since it is now overwritten in
every possible case.

>  	uint32_t tag;
>  
> +	/*
> +	 * Get the root node if asked for next node from the root node
> +	 * (offset == 0) or if the given offset is not valid (negative).
> +	 */
> +	if (offset <= 0) {
> +		nextoffset = fdt_root_offset(fdt);
> +		if (nextoffset < 0)
> +			return nextoffset;
> +	}
> +

The various changes you've made look correct, but I don't love the
fact that it requires nearly every function which takes a node offset
to be altered non-trivially.  As well as making for a large diff, it
strikes me as fragile - a bit of logic that could easily be forgotten
on a new function.

I think we want to move the offset 0 handling into a common helper.
Logically that should be fdt_check_node_offset_(), since that's the
standard way of validating a node offset parameter.  As you've pointed
out, that doesn't work with the current signature/semantics of
fdt_check_node_offset_() - but it's a strictly internal function, so
we can alter its signature freely.

>  	if (offset >= 0)
> -		if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
> +		if ((nextoffset = fdt_check_node_offset_(fdt, nextoffset)) < 0)
>  			return nextoffset;
>  
>  	do {
> diff --git a/libfdt/fdt_ro.c b/libfdt/fdt_ro.c
> index 11f2e2ee..856c62f1 100644
> --- a/libfdt/fdt_ro.c
> +++ b/libfdt/fdt_ro.c
> @@ -231,6 +231,12 @@ int fdt_subnode_offset_namelen(const void *fdt, int offset,
>  
>  	FDT_RO_PROBE(fdt);
>  
> +	if (!offset) {
> +		offset = fdt_root_offset(fdt);
> +		if (offset < 0)
> +			return offset;
> +	}
> +
>  	for (depth = 0;
>  	     (offset >= 0) && (depth >= 0);
>  	     offset = fdt_next_node(fdt, offset, &depth))
> @@ -253,13 +259,17 @@ int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen)
>  {
>  	const char *end = path + namelen;
>  	const char *p = path;
> -	int offset = 0;
> +	int offset;
>  
>  	FDT_RO_PROBE(fdt);
>  
>  	if (!can_assume(VALID_INPUT) && namelen <= 0)
>  		return -FDT_ERR_BADPATH;
>  
> +	offset = fdt_root_offset(fdt);
> +	if (offset < 0)
> +		return offset;
> +
>  	/* see if we have an alias */
>  	if (*path != '/') {
>  		const char *q = memchr(path, '/', end - p);
> @@ -304,14 +314,24 @@ int fdt_path_offset(const void *fdt, const char *path)
>  
>  const char *fdt_get_name(const void *fdt, int nodeoffset, int *len)
>  {
> -	const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset);
> +	const struct fdt_node_header *nh;
>  	const char *nameptr;
>  	int err;
>  
> +	if (!nodeoffset) {
> +		nodeoffset = fdt_root_offset(fdt);
> +		if (nodeoffset < 0) {
> +			err = nodeoffset;
> +			goto fail;
> +		}
> +	}
> +
> +
>  	if (!can_assume(VALID_DTB) && (((err = fdt_ro_probe_(fdt)) < 0)
>  	    || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0)))
>  			goto fail;
>  
> +	nh = fdt_offset_ptr_(fdt, nodeoffset);
>  	nameptr = nh->name;
>  
>  	if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) {
> @@ -344,6 +364,12 @@ int fdt_first_property_offset(const void *fdt, int nodeoffset)
>  {
>  	int offset;
>  
> +	if (!nodeoffset) {
> +		nodeoffset = fdt_root_offset(fdt);
> +		if (nodeoffset < 0)
> +			return nodeoffset;
> +	}
> +
>  	if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0)
>  		return offset;
>  
> @@ -581,7 +607,7 @@ int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen)
>  	if (buflen < 2)
>  		return -FDT_ERR_NOSPACE;
>  
> -	for (offset = 0, depth = 0;
> +	for (offset = fdt_root_offset(fdt), depth = 0;
>  	     (offset >= 0) && (offset <= nodeoffset);
>  	     offset = fdt_next_node(fdt, offset, &depth)) {
>  		while (pdepth > depth) {
> @@ -619,7 +645,7 @@ int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen)
>  	else if (offset == -FDT_ERR_BADOFFSET)
>  		return -FDT_ERR_BADSTRUCTURE;
>  
> -	return offset; /* error from fdt_next_node() */
> +	return offset; /* error from fdt_next_node() or fdt_root_offset() */
>  }
>  
>  int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset,
> @@ -627,13 +653,21 @@ int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset,
>  {
>  	int offset, depth;
>  	int supernodeoffset = -FDT_ERR_INTERNAL;
> +	int root_offset;
>  
>  	FDT_RO_PROBE(fdt);
>  
>  	if (supernodedepth < 0)
>  		return -FDT_ERR_NOTFOUND;
>  
> -	for (offset = 0, depth = 0;
> +	root_offset = fdt_root_offset(fdt);
> +	if (root_offset < 0)
> +		return root_offset;
> +
> +	if (!nodeoffset)
> +		nodeoffset = root_offset;
> +
> +	for (offset = root_offset, depth = 0;

Do you need this special casing? Won't the fact you've update
fdt_next_node() to handle the offset 0 case be enough?

>  	     (offset >= 0) && (offset <= nodeoffset);
>  	     offset = fdt_next_node(fdt, offset, &depth)) {
>  		if (depth == supernodedepth)
> @@ -663,12 +697,15 @@ int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset,
>  int fdt_node_depth(const void *fdt, int nodeoffset)
>  {
>  	int nodedepth;
> -	int err;
> +	int offset;
> +
> +	offset = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth);
> +	if (offset < 0)
> +		return offset;
> +
> +	if (!can_assume(LIBFDT_FLAWLESS) && offset != fdt_root_offset(fdt))
> +		return -FDT_ERR_INTERNAL;
>  
> -	err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth);
> -	if (err)
> -		return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err :
> -			-FDT_ERR_INTERNAL;

Replacing the extremly cryptic logic above is a nice byproduct.

>  	return nodedepth;
>  }
>  
> diff --git a/libfdt/fdt_rw.c b/libfdt/fdt_rw.c
> index 850aafe4..ceef49b8 100644
> --- a/libfdt/fdt_rw.c
> +++ b/libfdt/fdt_rw.c
> @@ -226,6 +226,12 @@ static int fdt_add_property_(void *fdt, int nodeoffset, const char *name,
>  	int err;
>  	int allocated;
>  
> +	if (!nodeoffset) {
> +		nodeoffset = fdt_root_offset(fdt);
> +		if (nodeoffset < 0)
> +			return nodeoffset;
> +	}
> +
>  	if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0)
>  		return nextoffset;
>  
> @@ -377,6 +383,12 @@ int fdt_add_subnode_namelen(void *fdt, int parentoffset,
>  
>  	FDT_RW_PROBE(fdt);
>  
> +	if (!parentoffset) {
> +		parentoffset = fdt_root_offset(fdt);
> +		if (parentoffset < 0)
> +			return parentoffset;
> +	}
> +
>  	offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen);
>  	if (offset >= 0)
>  		return -FDT_ERR_EXISTS;
> diff --git a/libfdt/libfdt.h b/libfdt/libfdt.h
> index c69a18ed..7a1915a5 100644
> --- a/libfdt/libfdt.h
> +++ b/libfdt/libfdt.h
> @@ -503,6 +503,19 @@ int fdt_num_mem_rsv(const void *fdt);
>   */
>  int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size);
>  
> +/**
> + * fdt_root_offset - Get the offset of the root node
> + * @fdt: pointer to the device tree blob
> + *
> + * The root node can be located after the offset 0. Indeed FDT_NOP tags can be
> + * present at offset 0. fdt_root_offset() takes care of those possible FDT_NOP
> + * tags.
> + *
> + * returns: offset of the root node or negative libfdt error value otherwise
> + */
> +int fdt_root_offset(const void *fdt);
> +
> +
>  /**
>   * fdt_subnode_offset_namelen - find a subnode based on substring
>   * @fdt: pointer to the device tree blob
> @@ -1025,7 +1038,7 @@ int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen);
>   * at a specific depth from the root (where the root itself has depth
>   * 0, its immediate subnodes depth 1 and so forth).  So
>   *	fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL);
> - * will always return 0, the offset of the root node.  If the node at
> + * will always return the offset of the root node.  If the node at
>   * nodeoffset has depth D, then:
>   *	fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL);
>   * will return nodeoffset itself.
> diff --git a/libfdt/version.lds b/libfdt/version.lds
> index cbfef546..d0b71669 100644
> --- a/libfdt/version.lds
> +++ b/libfdt/version.lds
> @@ -7,6 +7,7 @@ LIBFDT_1.2 {
>  		fdt_string;
>  		fdt_num_mem_rsv;
>  		fdt_get_mem_rsv;
> +		fdt_root_offset;
>  		fdt_subnode_offset_namelen;
>  		fdt_subnode_offset;
>  		fdt_path_offset_namelen;
> -- 
> 2.55.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 --]

  reply	other threads:[~2026-08-30  3:21 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26  8:31 [PATCH v3 00/15] Add support for structured tags and v18 dtb version Herve Codina
2026-08-26  8:31 ` [PATCH v3 01/15] fdtget: Use libfdt iterators instead of open coded loops Herve Codina
2026-08-27  3:55   ` David Gibson
2026-08-26  8:31 ` [PATCH v3 02/15] libfdt: Don't assume the root node is available at offset 0 Herve Codina
2026-08-30  3:21   ` David Gibson [this message]
2026-08-31 12:01     ` Herve Codina
2026-09-01  7:42       ` David Gibson
2026-09-01 12:18         ` Herve Codina
2026-09-02  7:06           ` David Gibson
2026-08-26  8:31 ` [PATCH v3 03/15] tests: " Herve Codina
2026-09-01  8:03   ` David Gibson
2026-09-01 13:36     ` Herve Codina
2026-09-02  8:56       ` David Gibson
2026-08-26  8:31 ` [PATCH v3 04/15] tests/nopulate: Add a FDT_NOP before the root node Herve Codina
2026-09-01  8:05   ` David Gibson
2026-08-26  8:31 ` [PATCH v3 05/15] tests: treegen: Introduce emit_fdt_header_vers() Herve Codina
2026-08-26  8:31 ` [PATCH v3 06/15] Introduce structured tag value definition Herve Codina
2026-08-26  8:31 ` [PATCH v3 07/15] fdtdump: Handle unknown tags Herve Codina
2026-08-26  8:31 ` [PATCH v3 08/15] flattree: " Herve Codina
2026-08-26  8:31 ` [PATCH v3 09/15] libfdt: Handle unknown tags in fdt_next_tag() Herve Codina
2026-08-26  8:31 ` [PATCH v3 10/15] libfdt: Introduce fdt_ptr_offset_() Herve Codina
2026-08-26  8:31 ` [PATCH v3 11/15] libfdt: Introduce fdt_getprop_by_offset_w() Herve Codina
2026-08-26  8:31 ` [PATCH v3 12/15] libfdt: Introduce fdt_getprop_offset_namelen() Herve Codina
2026-08-26  8:31 ` [PATCH v3 13/15] tests: Add wip_func utility Herve Codina
2026-08-26  8:31 ` [PATCH v3 14/15] libfdt: Handle unknown tags on dtb modifications Herve Codina
2026-08-26  8:31 ` [PATCH v3 15/15] Introduce v18 dtb version 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=apOhk-NQusFgV5Sx@gractus.seuss \
    --to=david@gibson.dropbear.id.au \
    --cc=Frank.Li@nxp.com \
    --cc=ayush@beagleboard.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree-compiler@vger.kernel.org \
    --cc=devicetree-spec@vger.kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=geert@linux-m68k.org \
    --cc=herve.codina@bootlin.com \
    --cc=hui.pu@gehealthcare.com \
    --cc=ian.ray@gehealthcare.com \
    --cc=krzk@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luca.ceresoli@bootlin.com \
    --cc=robh@kernel.org \
    --cc=thomas.petazzoni@bootlin.com \
    /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