From 9f6d57eaef77a17fafe0d28e4a103786ae2de55f Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:12:58 +0400 Subject: pylibfdt: Cast property length to Py_ssize_t Commit 7d01bb1c5a1d ("libfdt: Fix build with python 3.10") took only half of the upstream fix, dtc commit 383e148b70a4 ("pylibfdt: fix with Python 3.10"): it defines PY_SSIZE_T_CLEAN but leaves the length argument passed to Py_BuildValue() as a plain int. With PY_SSIZE_T_CLEAN in effect the "y#" and "s#" converters read a Py_ssize_t, so passing an int is undefined behaviour that happens to work only where the two share a representation. Take the remaining hunk so the typemap matches upstream and the subsequent upstream imports apply verbatim. Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 326a15c7941..eb075c0b92b 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -1049,9 +1049,9 @@ typedef uint32_t fdt32_t; $result = Py_None; else %#if PY_VERSION_HEX >= 0x03000000 - $result = Py_BuildValue("y#", $1, *arg4); + $result = Py_BuildValue("y#", $1, (Py_ssize_t)*arg4); %#else - $result = Py_BuildValue("s#", $1, *arg4); + $result = Py_BuildValue("s#", $1, (Py_ssize_t)*arg4); %#endif } -- cgit v1.3.1 From b737e5cf2bc8d9bd545451a3a2876465bbafb8fa Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:12:59 +0400 Subject: pylibfdt: Restore the upstream bytearray() in getprop() Commit 903fe17aa8c8 ("pylibfdt: Sync up with upstream") replaced the bytearray() that dtc uses here with bytes(), without saying why. The result is the same either way, since Property derives from bytearray and copies whatever it is handed, so restore the upstream spelling to keep the shipped file mechanically comparable with dtc. Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index eb075c0b92b..55170431357 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -421,7 +421,7 @@ class FdtRo(object): quiet) if isinstance(pdata, (int)): return pdata - return Property(prop_name, bytes(pdata[0])) + return Property(prop_name, bytearray(pdata[0])) def get_phandle(self, nodeoffset): """Get the phandle of a node -- cgit v1.3.1 From 04e670ea3630efadd654a940d893137a3c6c2c9e Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 24 Aug 2026 17:13:00 +0400 Subject: pylibfdt: Fix Python crash on getprop deallocation Fatal Python error: none_dealloc: deallocating None Python runtime state: finalizing (tstate=0x000055c9bac70920) Current thread 0x00007fbe34e47740 (most recent call first): Aborted (core dumped) This is caused by a missing Py_INCREF on the returned Py_None, as demonstrated e.g. in https://github.com/mythosil/swig-python-incref or described at https://edcjones.tripod.com/refcount.html ("Remember to INCREF Py_None!") A PoC for triggering this crash is uploaded to https://github.com/z3ntu/pylibfdt-crash . With this patch applied to pylibfdt the crash does not happen. This is a backport of dtc commit d152126bb029 ("Fix Python crash on getprop deallocation"). Signed-off-by: Luca Weiss Reviewed-by: Simon Glass Signed-off-by: David Gibson [adapted to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 55170431357..5025893b1e9 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -1045,14 +1045,16 @@ typedef uint32_t fdt32_t; /* typemap used for fdt_getprop() */ %typemap(out) (const void *) { - if (!$1) + if (!$1) { $result = Py_None; - else + Py_INCREF($result); + } else { %#if PY_VERSION_HEX >= 0x03000000 $result = Py_BuildValue("y#", $1, (Py_ssize_t)*arg4); %#else $result = Py_BuildValue("s#", $1, (Py_ssize_t)*arg4); %#endif + } } /* typemap used for fdt_setprop() */ -- cgit v1.3.1 From d0c7b9e65bfd30b93aadbd5d0143b2911f8278c1 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 24 Aug 2026 17:13:01 +0400 Subject: pylibfdt: Add Property.as_stringlist() Add a new method for decoding a string list property, useful for e.g. the "reg-names" property. This is a backport of dtc commit 83102717d7c4 ("pylibfdt: add Property.as_stringlist()"), without the dtc-side test, which has no counterpart in U-Boot's test suite. Signed-off-by: Luca Weiss Signed-off-by: David Gibson [adapted to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 5025893b1e9..c8ac578b367 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -728,6 +728,13 @@ class Property(bytearray): raise ValueError('Property contains embedded nul characters') return self[:-1].decode('utf-8') + def as_stringlist(self): + """Unicode is supported by decoding from UTF-8""" + if self[-1] != 0: + raise ValueError('Property lacks nul termination') + parts = self[:-1].split(b'\x00') + return list(map(lambda x: x.decode('utf-8'), parts)) + class FdtSw(FdtRo): """Software interface to create a device tree from scratch -- cgit v1.3.1 From f41cecc1fe9442939f1a81684db1f467f3bd4aba Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 24 Aug 2026 17:13:02 +0400 Subject: pylibfdt: Add Property.as_*int*_array() Add new methods to handle decoding of int32, uint32, int64 and uint64 arrays. This is a backport of dtc commit a04f69025003 ("pylibfdt: add Property.as_*int*_array()"), without the dtc-side tests, which have no counterpart in U-Boot's test suite. Signed-off-by: Luca Weiss Signed-off-by: David Gibson [adapted to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index c8ac578b367..735a3bab6d9 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -720,6 +720,21 @@ class Property(bytearray): def as_int64(self): return self.as_cell('q') + def as_list(self, fmt): + return list(map(lambda x: x[0], struct.iter_unpack('>' + fmt, self))) + + def as_uint32_list(self): + return self.as_list('L') + + def as_int32_list(self): + return self.as_list('l') + + def as_uint64_list(self): + return self.as_list('Q') + + def as_int64_list(self): + return self.as_list('q') + def as_str(self): """Unicode is supported by decoding from UTF-8""" if self[-1] != 0: -- cgit v1.3.1 From 66ccb13a8180bf7d09d79ca136b18301e020ae5e Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 24 Aug 2026 17:13:03 +0400 Subject: pylibfdt: Add FdtRo.get_path() Add a new Python method wrapping fdt_get_path() from the C API. This is a backport of dtc commit ed310803ea89 ("pylibfdt: add FdtRo.get_path()"), without the dtc-side test, which has no counterpart in U-Boot's test suite. Signed-off-by: Luca Weiss Reviewed-by: Simon Glass Signed-off-by: David Gibson [adapted to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 735a3bab6d9..634ccd2a829 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -447,6 +447,29 @@ class FdtRo(object): """ return fdt_get_alias(self._fdt, name) + def get_path(self, nodeoffset, quiet=()): + """Get the full path of a node + + Args: + nodeoffset: Node offset to check + + Returns: + Full path to the node + + Raises: + FdtException if an error occurs + """ + size = 1024 + while True: + ret, path = fdt_get_path(self._fdt, nodeoffset, size) + if ret == -NOSPACE: + size = size * 2 + continue + err = check_err(ret, quiet) + if err: + return err + return path + def parent_offset(self, nodeoffset, quiet=()): """Get the offset of a node's parent @@ -1120,6 +1143,11 @@ typedef uint32_t fdt32_t; } } +%include "cstring.i" + +%cstring_output_maxsize(char *buf, int buflen); +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + /* We have both struct fdt_property and a function fdt_property() */ %warnfilter(302) fdt_property; -- cgit v1.3.1 From ba6d20256305ecff8c879e2acb169aa5b0fba933 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 24 Aug 2026 17:13:04 +0400 Subject: pylibfdt: Work-around SWIG limitations with flexible arrays SWIG cannot generate setters for a struct's flexible array member and emits C that does not compile: ./pylibfdt/libfdt_wrap.c: In function '_wrap_fdt_node_header_name_set': ./pylibfdt/libfdt_wrap.c:4350:18: error: cast specifies array type ./pylibfdt/libfdt_wrap.c:4350:16: error: invalid use of flexible array member ./pylibfdt/libfdt_wrap.c:4613:18: error: cast specifies array type ./pylibfdt/libfdt_wrap.c:4613:16: error: invalid use of flexible array member Turns out this is a known issue with SWIG: https://github.com/swig/swig/issues/1699 Implement the work-around to ignore the flexible array member. U-Boot's copy of fdt.h still declares those members as zero-length arrays, so nothing breaks today, but carrying the work-around now keeps the shipped file in step with upstream and avoids the failure when the C libfdt is next resynced. This is a backport of dtc commit abbd523bae6e ("pylibfdt: Work-around SWIG limitations with flexible arrays"). Signed-off-by: Rob Herring Reviewed-by: Simon Glass Tested-by: Simon Glass Signed-off-by: David Gibson [adapted to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 634ccd2a829..2a7fdfb2af4 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -1041,6 +1041,9 @@ class NodeAdder(): %rename(fdt_property) fdt_property_func; +%immutable fdt_property::data; +%immutable fdt_node_header::name; + /* * fdt32_t is a big-endian 32-bit value defined to uint32_t in libfdt_env.h * so use the same type here. -- cgit v1.3.1 From 69c95e9e4f576b811e640d1bf0ee95cf070dd542 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 24 Aug 2026 17:13:05 +0400 Subject: pylibfdt: Add size_hint parameter for get_path Let the caller pick the initial buffer size, which also makes the -NOSPACE retry path reachable from a test by passing a tiny hint. This is a backport of dtc commit 3f29d6d85c24 ("pylibfdt: add size_hint parameter for get_path"). Signed-off-by: Luca Weiss Signed-off-by: David Gibson [adapt to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 2a7fdfb2af4..6a900265459 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -447,11 +447,12 @@ class FdtRo(object): """ return fdt_get_alias(self._fdt, name) - def get_path(self, nodeoffset, quiet=()): + def get_path(self, nodeoffset, size_hint=1024, quiet=()): """Get the full path of a node Args: nodeoffset: Node offset to check + size_hint: Hint for size of returned string Returns: Full path to the node @@ -459,11 +460,10 @@ class FdtRo(object): Raises: FdtException if an error occurs """ - size = 1024 while True: - ret, path = fdt_get_path(self._fdt, nodeoffset, size) + ret, path = fdt_get_path(self._fdt, nodeoffset, size_hint) if ret == -NOSPACE: - size = size * 2 + size_hint *= 2 continue err = check_err(ret, quiet) if err: -- cgit v1.3.1 From 3bf9e7e72ab545d08d9ae30bcb45027ad24609bb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 24 Aug 2026 17:13:06 +0400 Subject: pylibfdt: Support boolean properties Boolean properties are unusual in that their presense or absence indicates the value of the property. This makes them a little painful to support using the existing getprop() support. Add new methods to deal with booleans specifically. This is a backport of dtc commit 52157f13ef3d ("pylibfdt: Support boolean properties"), without the dtc-side tests, which have no counterpart in U-Boot's test suite. Signed-off-by: Simon Glass Signed-off-by: David Gibson [adapt to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 6a900265459..dd568fc7493 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -423,6 +423,35 @@ class FdtRo(object): return pdata return Property(prop_name, bytearray(pdata[0])) + def hasprop(self, nodeoffset, prop_name, quiet=()): + """Check if a node has a property + + This can be used to check boolean properties + + Args: + nodeoffset: Node offset containing property to check + prop_name: Name of property to check + quiet: Errors to ignore (empty to raise on all errors). Note that + NOTFOUND is added internally by this function so need not be + provided + + Returns: + True if the property exists in the node, else False. If an error + other than -NOTFOUND is returned by fdt_getprop() then the error + is return (-ve integer) + + Raises: + FdtError if any error occurs other than NOTFOUND (e.g. the + nodeoffset is invalid) + """ + pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), + quiet + (NOTFOUND,)) + if isinstance(pdata, (int)): + if pdata == -NOTFOUND: + return False + return pdata + return True + def get_phandle(self, nodeoffset): """Get the phandle of a node @@ -609,6 +638,32 @@ class Fdt(FdtRo): return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, val, len(val)), quiet) + def setprop_bool(self, nodeoffset, prop_name, val, quiet=()): + """Set the boolean value of a property + + Either: + adds the property if not already present; or + deletes the property if present + + Args: + nodeoffset: Node offset containing the property to create/delete + prop_name: Name of property + val: Boolean value to write (i.e. True or False) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + exists = self.hasprop(nodeoffset, prop_name, quiet) + if val != exists: + if val: + return self.setprop(nodeoffset, prop_name, b'', quiet=quiet) + else: + return self.delprop(nodeoffset, prop_name, quiet=quiet) + def setprop_u32(self, nodeoffset, prop_name, val, quiet=()): """Set the value of a property -- cgit v1.3.1 From 5b7d682e1d28383318c6d649d11aef45ed604ce8 Mon Sep 17 00:00:00 2001 From: Brandon Maier Date: Mon, 24 Aug 2026 17:13:07 +0400 Subject: pylibfdt: Fix get_mem_rsv for newer Python versions The test for get_mem_rsv fails on newer versions of Python with the following error. > AssertionError: Lists differ: > [16045690981097406464, 1048576] != [0, 16045690981097406464, 1048576] It appears this is because the PyTuple_GET_SIZE() function that was used to build the fdt_get_mem_rsv() return value has changed. It now is returning a non-zero value when it's passed an integer, which causes the SWIG wrapper to append the returned arguments to the return error rather then ignore them. This is valid behaviour per Python's documentation, which says it will "Return the size of the tuple p, which must be non-NULL and point to a tuple; no error checking is performed"[1]. As passing an integer is not a tuple, its return value is undefined. Fix this issue on older and newer versions by avoiding PyTuple_GET_SIZE() entirely. Always append the arguments to the list, and instead use the wrapper python function to check the first argument and then splice the last two arguments as the return value. [1] https://docs.python.org/3/c-api/tuple.html#c.PyTuple_GET_SIZE This is a backport of dtc commit 822123856980 ("pylibfdt: fix get_mem_rsv for newer Python versions"), keeping the SWIG_AppendOutput() spelling introduced by commit a63456b9191f ("scripts/dtc/pylibfdt/libfdt.i_shipped: Use SWIG_AppendOutput"). Signed-off-by: Brandon Maier Signed-off-by: David Gibson [adapt to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index dd568fc7493..0204708aa6f 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -299,7 +299,9 @@ class FdtRo(object): Returns: Number of memory reserve-map records """ - return check_err(fdt_get_mem_rsv(self._fdt, index), quiet) + val = fdt_get_mem_rsv(self._fdt, index) + check_err(val[0], quiet) + return val[1:] def subnode_offset(self, parentoffset, name, quiet=()): """Get the offset of a named subnode @@ -1193,12 +1195,7 @@ typedef uint32_t fdt32_t; %typemap(argout) uint64_t * { PyObject *val = PyLong_FromUnsignedLongLong(*arg$argnum); - if (!result) { - if (PyTuple_GET_SIZE(resultobj) == 0) - resultobj = val; - else - resultobj = SWIG_AppendOutput(resultobj, val); - } + resultobj = SWIG_AppendOutput(resultobj, val); } %include "cstring.i" -- cgit v1.3.1 From 0e9f9fda2e4568aae651147c1e59a0b4c394df55 Mon Sep 17 00:00:00 2001 From: Brandon Maier Date: Mon, 24 Aug 2026 17:13:08 +0400 Subject: pylibfdt: Fix backwards compatibility of return values When our Python functions wrap `fdt_getprop()` they return a list containing `[*data, length]`. In SWIG v4.2 and earlier SWIG would discard `*data` if it is NULL/None. Causing the return value to just be `length`. But starting in SWIG v4.3 it no longer discards `*data`. So the return value is now `[None, length]`. Handle this compatibility issue in libfdt.i by checking if the return value looks like the older 4.2 return value, and casting it to the newer style. See https://github.com/swig/swig/pull/2907 This is a backport of dtc commit 9a969f3b70b0 ("pylibfdt/libfdt.i: fix backwards compatibility of return values"). Its prerequisite is already in tree as commit a63456b9191f ("scripts/dtc/pylibfdt/libfdt.i_shipped: Use SWIG_AppendOutput"). Signed-off-by: Brandon Maier Signed-off-by: David Gibson [adapt to U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 0204708aa6f..2c8efd57a60 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -118,11 +118,14 @@ def check_err_null(val, quiet=()): FdtException if val indicates an error was reported and the error is not in @quiet. """ - # Normally a list is returned which contains the data and its length. - # If we get just an integer error code, it means the function failed. + # Compatibility for SWIG v4.2 and earlier. SWIG 4.2 would drop the first + # item from the list if it was None, returning only the second item. if not isinstance(val, list): - if -val not in quiet: - raise FdtException(val) + val = [None, val] + + if val[0] is None: + if -val[1] not in quiet: + raise FdtException(val[1]) return val class FdtRo(object): @@ -399,8 +402,8 @@ class FdtRo(object): """ pdata = check_err_null( fdt_get_property_by_offset(self._fdt, prop_offset), quiet) - if isinstance(pdata, (int)): - return pdata + if pdata[0] is None: + return pdata[1] return Property(pdata[0], pdata[1]) def getprop(self, nodeoffset, prop_name, quiet=()): @@ -421,8 +424,8 @@ class FdtRo(object): """ pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), quiet) - if isinstance(pdata, (int)): - return pdata + if pdata[0] is None: + return pdata[1] return Property(prop_name, bytearray(pdata[0])) def hasprop(self, nodeoffset, prop_name, quiet=()): @@ -448,10 +451,10 @@ class FdtRo(object): """ pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), quiet + (NOTFOUND,)) - if isinstance(pdata, (int)): - if pdata == -NOTFOUND: + if pdata[0] is None: + if pdata[1] == -NOTFOUND: return False - return pdata + return pdata[1] return True def get_phandle(self, nodeoffset): -- cgit v1.3.1 From dfba3eff563cc0bb779791b10e8f78a124f4182e Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 24 Aug 2026 17:13:09 +0400 Subject: pylibfdt: Fix a typo in the next_node() docstring Spell "Tuple" correctly. This is the pylibfdt part of dtc commit 205fbef17b7b ("Fix some typos"); the other files that commit touches carry no such typos in tree. Signed-off-by: Thomas Huth Signed-off-by: David Gibson [adapt to U-Boot, rewrite commit message accordingly] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 2c8efd57a60..2179515a144 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -166,7 +166,7 @@ class FdtRo(object): quiet: Errors to ignore (empty to raise on all errors) Returns: - Typle: + Tuple: Offset of the next node, if any, else a -ve error Depth of the returned node, if any, else undefined -- cgit v1.3.1 From f3e00d4b7f365929e18395194049d569d2288f31 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:13:10 +0400 Subject: pylibfdt: Grow the FdtSw buffer geometrically Every expansion copies the whole tree into a freshly allocated buffer, so growing by a fixed amount makes building a tree cost time quadratic in its size. This is especially painful when assembling larger FIT images with binman, as it assembles the image with the data inline. Grow by at least as much as the tree already holds, which is what variable sized arrays usually do specifically to avoid such excessive copying. With this change, building a Rockchip TF-A+Falcon image whose FIT carries a 31 MiB kernel takes 33.1 s rather than 44.4 s, with binman itself down from 25.3 s to 14.0 s, as 7139 reallocations become 187. The images produced are byte-identical and the binman and dtoc test results are unaffected. This is a backport of dtc commit 0748c384fde6 ("pylibfdt: Grow the FdtSw buffer geometrically"). Link: https://github.com/dgibson/dtc/pull/189 Reviewed-by: Simon Glass Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 2179515a144..c00e74a46b7 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -861,7 +861,7 @@ class FdtSw(FdtRo): device tree. This will be increased automatically as needed as new items are added to the tree. """ - INC_SIZE = 1024 # Expand size by this much when out of space + INC_SIZE = 1024 # Expand size by at least this much when out of space def __init__(self, size_hint=None): """Create a new FdtSw object @@ -906,6 +906,10 @@ class FdtSw(FdtRo): -NOSPACE then the FDT will be expanded to have more space, and True will be returned, indicating that the operation needs to be tried again. + Each expansion copies the whole tree into a new buffer, so the size is + at least doubled rather than grown by a fixed amount, to keep the total + amount of copying proportional to the size of the tree. + Args: val: Return value from the operation that was attempted @@ -913,7 +917,7 @@ class FdtSw(FdtRo): True if the operation must be retried, else False """ if check_err(val, QUIET_NOSPACE) < 0: - self.resize(len(self._fdt) + self.INC_SIZE) + self.resize(len(self._fdt) + max(len(self._fdt), self.INC_SIZE)) return True return False -- cgit v1.3.1 From 34fddfa16d47bd97f5b11d6b36dcbda91037e02f Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:13:11 +0400 Subject: pylibfdt: Add address_cells() and size_cells() Finding out how many cells a node's children use for addresses and sizes currently requires calling the raw fdt_address_cells()/fdt_size_cells() wrappers with the private FdtRo._fdt buffer, as the class exposes no methods for them. Add them to FdtRo alongside the other node accessors. This is a backport of dtc commit 3750493c8b0f ("pylibfdt: Add address_cells() and size_cells()"). Link: https://github.com/dgibson/dtc/pull/190 Signed-off-by: David Gibson Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index c00e74a46b7..1983ebaee33 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -306,6 +306,38 @@ class FdtRo(object): check_err(val[0], quiet) return val[1:] + def address_cells(self, nodeoffset, quiet=()): + """Return the number of address cells used by a node's children + + Args: + nodeoffset: Offset of the node to check + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Number of address cells used by the children of @nodeoffset + + Raises: + FdtException if the node has an invalid #address-cells property, + or another error occurs + """ + return check_err(fdt_address_cells(self._fdt, nodeoffset), quiet) + + def size_cells(self, nodeoffset, quiet=()): + """Return the number of size cells used by a node's children + + Args: + nodeoffset: Offset of the node to check + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Number of size cells used by the children of @nodeoffset + + Raises: + FdtException if the node has an invalid #size-cells property, or + another error occurs + """ + return check_err(fdt_size_cells(self._fdt, nodeoffset), quiet) + def subnode_offset(self, parentoffset, name, quiet=()): """Get the offset of a named subnode -- cgit v1.3.1 From 388137abfd0442c1bbe3df92277823d9070a2747 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:13:12 +0400 Subject: pylibfdt: Add add_mem_rsv() and del_mem_rsv() The memory reserve map can be read through num_mem_rsv() and get_mem_rsv(), but changing it requires calling the raw fdt_* wrappers with the private Fdt._fdt buffer, as the class exposes no methods for the write side. Add them next to the existing accessors. This is a backport of dtc commit 89c99ce78ac8 ("pylibfdt: Add add_mem_rsv() and del_mem_rsv()"). Link: https://github.com/dgibson/dtc/pull/190 Signed-off-by: David Gibson Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 1983ebaee33..127ec22de3f 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -640,6 +640,42 @@ class Fdt(FdtRo): del self._fdt[self.totalsize():] return err + def add_mem_rsv(self, addr, size, quiet=()): + """Add a memory reserve-map record + + This asks the client program not to use the given region of memory, + e.g. because something was loaded there. + + Args: + addr: Start address of the region to reserve + size: Size of the region to reserve, in bytes + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if there is no space for another record, or another + error occurs + """ + return check_err(fdt_add_mem_rsv(self._fdt, addr, size), quiet) + + def del_mem_rsv(self, index, quiet=()): + """Remove the indexed memory reserve-map record + + Args: + index: Record to remove (0=first) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if there is no record at @index, or another error + occurs + """ + return check_err(fdt_del_mem_rsv(self._fdt, index), quiet) + def set_name(self, nodeoffset, name, quiet=()): """Set the name of a node -- cgit v1.3.1 From 44a2a718fbe62d703a8ce2ff576d2252c7444087 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 24 Aug 2026 17:13:13 +0400 Subject: pylibfdt: Document that FdtSw.property() takes bytes Commit 97de532e59be ("pylibfdt: Correct the type for fdt_property_stub()") took two of the three hunks of the upstream change and left the docstring saying only "Value of property", which is what prompted the type fix in the first place. Take the remaining hunk of dtc commit fdf3f6d897ab ("pylibfdt: Correct the type for fdt_property_stub()"). Signed-off-by: Simon Glass Signed-off-by: David Gibson [rewrite commit message for U-Boot] Signed-off-by: Alexey Charkov --- scripts/dtc/pylibfdt/libfdt.i_shipped | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 127ec22de3f..3d527dfe212 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -1112,7 +1112,7 @@ class FdtSw(FdtRo): Args: name: Name of property to add - val: Value of property + val: Value of property (bytes) quiet: Errors to ignore (empty to raise on all errors) Raises: -- cgit v1.3.1 From 01fb2b4d7368483f95b6018b2c069de0b8ebadeb Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 24 Aug 2026 17:13:14 +0400 Subject: dtoc: Add tests for the pylibfdt bindings dtoc does not use Exercise additional pylibfdt bindings imported from upstream against the existing dtoc test tree, which already carries a boolean property, a string list, integer arrays and a bus node that overrides #address-cells and #size-cells. The FdtSw cases cover both halves of the geometric growth change: that an out-of-space result at least doubles the buffer, and that a tree needing several expansions still comes out intact. Signed-off-by: Alexey Charkov --- tools/dtoc/test_fdt.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index f141f931a94..4ab1365d531 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -10,6 +10,7 @@ Written by Simon Glass from argparse import ArgumentParser import os import shutil +import struct import sys import tempfile import unittest @@ -754,6 +755,178 @@ class TestProp(unittest.TestCase): self.dtb.GetFilename()) +class TestPylibfdt(unittest.TestCase): + """Tests for the parts of pylibfdt that dtoc does not itself use + + These bindings come from upstream dtc and have no coverage elsewhere in + U-Boot, so exercise them directly rather than through the Fdt wrapper. + """ + + @classmethod + def setUpClass(cls): + tools.prepare_output_dir(None) + + @classmethod + def tearDownClass(cls): + tools.finalise_output_dir() + + def setUp(self): + self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) + self.fdt = self.dtb.GetFdtObj() + self.node = self.fdt.path_offset('/spl-test') + + def test_hasprop(self): + """Test checking for the presence of a property""" + self.assertTrue(self.fdt.hasprop(self.node, 'boolval')) + self.assertTrue(self.fdt.hasprop(self.node, 'intval')) + self.assertFalse(self.fdt.hasprop(self.node, 'missing')) + + def test_hasprop_bad_node(self): + """Test that hasprop() still reports errors other than NOTFOUND""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.hasprop(-1, 'boolval') + self.assertEqual(-libfdt.BADOFFSET, exc.exception.err) + + def test_setprop_bool(self): + """Test creating and deleting a boolean property""" + # Leave room for the new property + self.fdt.resize(self.fdt.totalsize() + 1024) + node = self.fdt.path_offset('/spl-test') + + self.fdt.setprop_bool(node, 'newbool', True) + self.assertTrue(self.fdt.hasprop(node, 'newbool')) + self.assertEqual(b'', self.fdt.getprop(node, 'newbool')) + + # Setting it again should be a no-op + self.fdt.setprop_bool(node, 'newbool', True) + self.assertTrue(self.fdt.hasprop(node, 'newbool')) + + self.fdt.setprop_bool(node, 'newbool', False) + self.assertFalse(self.fdt.hasprop(node, 'newbool')) + + # Deleting it again should also be a no-op + self.fdt.setprop_bool(node, 'newbool', False) + self.assertFalse(self.fdt.hasprop(node, 'newbool')) + + def test_get_path(self): + """Test reading back the full path of a node""" + node = self.fdt.path_offset('/i2c@0/pmic@9') + self.assertEqual('/i2c@0/pmic@9', self.fdt.get_path(node)) + self.assertEqual('/', self.fdt.get_path(0)) + + def test_get_path_no_space(self): + """Test that get_path() retries with a larger buffer as needed""" + node = self.fdt.path_offset('/i2c@0/pmic@9') + self.assertEqual('/i2c@0/pmic@9', self.fdt.get_path(node, size_hint=1)) + + def test_get_path_bad_node(self): + """Test get_path() on an invalid node offset""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.get_path(-1) + self.assertEqual(-libfdt.BADOFFSET, exc.exception.err) + + def test_as_stringlist(self): + """Test decoding a string-list property""" + prop = self.fdt.getprop(self.node, 'stringarray') + self.assertEqual(['multi-word', 'message'], prop.as_stringlist()) + + prop = self.fdt.getprop(self.node, 'stringval') + self.assertEqual(['message'], prop.as_stringlist()) + + def test_as_int_lists(self): + """Test decoding integer-array properties""" + prop = self.fdt.getprop(self.node, 'intarray') + self.assertEqual([2, 3, 4], prop.as_uint32_list()) + self.assertEqual([2, 3, 4], prop.as_int32_list()) + + prop = self.fdt.getprop(self.node, 'int64val') + self.assertEqual([0x123456789abcdef0], prop.as_uint64_list()) + self.assertEqual([0x123456789abcdef0], prop.as_int64_list()) + + def test_as_int_lists_negative(self): + """Test that the signed accessors differ from the unsigned ones""" + self.fdt.resize(self.fdt.totalsize() + 1024) + node = self.fdt.path_offset('/spl-test') + + self.fdt.setprop(node, 'negs32', struct.pack('>ll', -1, -2)) + prop = self.fdt.getprop(node, 'negs32') + self.assertEqual([-1, -2], prop.as_int32_list()) + self.assertEqual([0xffffffff, 0xfffffffe], prop.as_uint32_list()) + + self.fdt.setprop(node, 'negs64', struct.pack('>qq', -1, -2)) + prop = self.fdt.getprop(node, 'negs64') + self.assertEqual([-1, -2], prop.as_int64_list()) + self.assertEqual([0xffffffffffffffff, 0xfffffffffffffffe], + prop.as_uint64_list()) + + def test_address_and_size_cells(self): + """Test reading the cell counts a node uses for its children""" + self.assertEqual(1, self.fdt.address_cells(0)) + self.assertEqual(1, self.fdt.size_cells(0)) + + # i2c@0 overrides both + i2c = self.fdt.path_offset('/i2c@0') + self.assertEqual(1, self.fdt.address_cells(i2c)) + self.assertEqual(0, self.fdt.size_cells(i2c)) + + def test_mem_rsv(self): + """Test adding, reading back and deleting reserve-map records""" + self.assertEqual(0, self.fdt.num_mem_rsv()) + + # Leave room for the new records + self.fdt.resize(self.fdt.totalsize() + 1024) + self.fdt.add_mem_rsv(0xdeadbeef00000000, 0x100000) + self.fdt.add_mem_rsv(0x1000, 0x2000) + self.assertEqual(2, self.fdt.num_mem_rsv()) + self.assertEqual([0xdeadbeef00000000, 0x100000], + list(self.fdt.get_mem_rsv(0))) + self.assertEqual([0x1000, 0x2000], list(self.fdt.get_mem_rsv(1))) + + self.fdt.del_mem_rsv(0) + self.assertEqual(1, self.fdt.num_mem_rsv()) + self.assertEqual([0x1000, 0x2000], list(self.fdt.get_mem_rsv(0))) + + def test_del_mem_rsv_missing(self): + """Test deleting a reserve-map record which is not there""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.del_mem_rsv(0) + self.assertEqual(-libfdt.NOTFOUND, exc.exception.err) + + def test_fdtsw_growth(self): + """Test that running out of space at least doubles the buffer""" + fdtsw = libfdt.FdtSw() + size = len(fdtsw._fdt) + self.assertEqual(fdtsw.INC_SIZE, size) + + self.assertTrue(fdtsw.check_space(-libfdt.NOSPACE)) + self.assertEqual(size * 2, len(fdtsw._fdt)) + + self.assertTrue(fdtsw.check_space(-libfdt.NOSPACE)) + self.assertEqual(size * 4, len(fdtsw._fdt)) + + # Anything that is not an out-of-space error leaves the buffer alone + self.assertFalse(fdtsw.check_space(0)) + self.assertEqual(size * 4, len(fdtsw._fdt)) + + def test_fdtsw_build(self): + """Test building a tree large enough to need several expansions""" + fdtsw = libfdt.FdtSw() + fdtsw.finish_reservemap() + fdtsw.begin_node('') + for i in range(50): + fdtsw.begin_node(f'node{i}') + fdtsw.property('data', b'x' * 200) + fdtsw.end_node() + fdtsw.end_node() + + out = fdtsw.as_fdt() + self.assertGreater(out.totalsize(), fdtsw.INC_SIZE * 4) + for i in range(50): + node = out.path_offset(f'/node{i}') + self.assertEqual(f'/node{i}', out.get_path(node)) + self.assertEqual(b'x' * 200, out.getprop(node, 'data')) + + class TestFdtUtil(unittest.TestCase): """Tests for the fdt_util module @@ -970,7 +1143,7 @@ def run_tests(names, processes): test_name = names[0] if names else None result = test_util.run_test_suites( 'test_fdt', False, False, False, False, processes, test_name, None, - [TestFdt, TestNode, TestProp, TestFdtUtil]) + [TestFdt, TestNode, TestProp, TestPylibfdt, TestFdtUtil]) return (0 if result.wasSuccessful() else 1) -- cgit v1.3.1