From 5008d1d6a356b8d0a78060da2e1021507d529cff Mon Sep 17 00:00:00 2001 From: Jitka Plesnikova Date: Wed, 29 Jul 2026 07:55:56 +0200 Subject: [PATCH 1/7] pylibfdt: Replace removed SWIG Python 2 compatibility macros SWIG 4.5.0 removed Python 2 compatibility macros (PyInt_*, PyString_*) from its runtime header pyhead.swg (see commit 79f7a2b7cb7d). Replace them with their Python 3 C API equivalents: - PyString_FromString -> PyUnicode_FromString - PyString_AsString -> PyBytes_AsString - PyInt_AsLong -> PyLong_AsLong The replacements are safe since the macros were already aliased to these exact functions in SWIG's Python 3 code path. Signed-off-by: Jitka Plesnikova Signed-off-by: Peter Robinson Signed-off-by: David Gibson --- pylibfdt/libfdt.i | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i index 1f9c047..b1cb987 100644 --- a/pylibfdt/libfdt.i +++ b/pylibfdt/libfdt.i @@ -1136,7 +1136,7 @@ typedef uint32_t fdt32_t; PyObject *buff; if ($1) { - resultobj = PyString_FromString( + resultobj = PyUnicode_FromString( fdt_string(fdt1, fdt32_to_cpu($1->nameoff))); buff = PyByteArray_FromStringAndSize( (const char *)($1 + 1), fdt32_to_cpu($1->len)); @@ -1169,13 +1169,13 @@ typedef uint32_t fdt32_t; } $1 = PyBytes_AsString($input); %#else - $1 = PyString_AsString($input); /* char *str */ + $1 = PyBytes_AsString($input); /* char *str */ %#endif } /* typemaps used for fdt_next_node() */ %typemap(in, numinputs=1) int *depth (int depth) { - depth = (int) PyInt_AsLong($input); + depth = (int) PyLong_AsLong($input); $1 = &depth; } From 0748c384fde63475b290f1fb365ae712f3e9feac Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 6 Aug 2026 13:21:40 +0400 Subject: [PATCH 2/7] 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 images with the data inline, such as U-Boot's binman does for FIT images. 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. Signed-off-by: Alexey Charkov Signed-off-by: David Gibson --- pylibfdt/libfdt.i | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i index b1cb987..1ab69fb 100644 --- a/pylibfdt/libfdt.i +++ b/pylibfdt/libfdt.i @@ -857,7 +857,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 @@ -901,6 +901,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 @@ -908,7 +912,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 From 3750493c8b0fc6082495edd79e249e0909891d0a Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 6 Aug 2026 13:18:13 +0400 Subject: [PATCH 3/7] 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, and test them. Signed-off-by: Alexey Charkov Signed-off-by: David Gibson --- pylibfdt/libfdt.i | 32 ++++++++++++++++++++++++++++++++ tests/pylibfdt_tests.py | 15 +++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i index 1ab69fb..e4c2803 100644 --- a/pylibfdt/libfdt.i +++ b/pylibfdt/libfdt.i @@ -302,6 +302,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 diff --git a/tests/pylibfdt_tests.py b/tests/pylibfdt_tests.py index 6af11f2..7178bb2 100644 --- a/tests/pylibfdt_tests.py +++ b/tests/pylibfdt_tests.py @@ -411,6 +411,21 @@ class PyLibfdtBasicTests(unittest.TestCase): self.fdt.get_mem_rsv(0)) self.assertEqual([123456789, 0o10000], self.fdt.get_mem_rsv(1)) + def testCells(self): + """Test that we can read #address-cells and #size-cells""" + self.assertEqual(1, self.fdt.address_cells(0)) + self.assertEqual(0, self.fdt.size_cells(0)) + + # A node without the properties inherits neither, so the libfdt + # defaults of 2 and 1 apply + node = self.fdt.path_offset('/subnode@1') + self.assertEqual(2, self.fdt.address_cells(node)) + self.assertEqual(1, self.fdt.size_cells(node)) + + node = self.fdt.path_offset('/subnode@2') + self.assertEqual(1, self.fdt.address_cells(node)) + self.assertEqual(0, self.fdt.size_cells(node)) + def testEmpty(self): """Test that we can create an empty tree""" self.assertEqual(-libfdt.NOSPACE, From 89c99ce78ac8e5ff10e829e21e6cffa12a6e1416 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 6 Aug 2026 13:19:02 +0400 Subject: [PATCH 4/7] 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, and test them. Signed-off-by: Alexey Charkov Signed-off-by: David Gibson --- pylibfdt/libfdt.i | 36 ++++++++++++++++++++++++++++++++++++ tests/pylibfdt_tests.py | 14 ++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i index e4c2803..b41c03f 100644 --- a/pylibfdt/libfdt.i +++ b/pylibfdt/libfdt.i @@ -636,6 +636,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 diff --git a/tests/pylibfdt_tests.py b/tests/pylibfdt_tests.py index 7178bb2..0e16607 100644 --- a/tests/pylibfdt_tests.py +++ b/tests/pylibfdt_tests.py @@ -411,6 +411,20 @@ class PyLibfdtBasicTests(unittest.TestCase): self.fdt.get_mem_rsv(0)) self.assertEqual([123456789, 0o10000], self.fdt.get_mem_rsv(1)) + def testAddDelReserveMap(self): + """Test that we can add to and remove from the memory reserve map""" + fdt = _ReadFdt('test_tree1.dtb') + fdt.resize(fdt.totalsize() + 64) + + self.assertEqual(0, fdt.add_mem_rsv(0x40000000, 0x2000)) + self.assertEqual(3, fdt.num_mem_rsv()) + self.assertEqual([0x40000000, 0x2000], fdt.get_mem_rsv(2)) + + self.assertEqual(0, fdt.del_mem_rsv(2)) + self.assertEqual(2, fdt.num_mem_rsv()) + self.assertEqual(-libfdt.NOTFOUND, + fdt.del_mem_rsv(2, QUIET_NOTFOUND)) + def testCells(self): """Test that we can read #address-cells and #size-cells""" self.assertEqual(1, self.fdt.address_cells(0)) From 8c37c14c95d963579ae7c32446a80a686d393b43 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Wed, 26 Aug 2026 10:31:32 +0200 Subject: [PATCH 5/7] fdtget: Use libfdt iterators instead of open coded loops fdtget uses directly fdt_{first,next}_property_offset() with a while(1) loop to iterate over node properties. It also uses the low level primitive fdt_next_tag() with custom tags, level and depth handling to iterates over subnodes. It is worth noting that FDT_NOP can be returned by fdt_next_tag() and this tag is not taken into account in the fdtget open coded loop. This will lead to an incorrect error if a FDT_NOP tag is encountered. libfdt provides iterators to iterate over node properties and subnodes. The subnode iterator provided by libfdt is robust against FDT_NOP tags and will be robust in the future when new tags are introduced. Replace fdtget open coded loops by iterators provided by libfdt and designed to perform those operations. Signed-off-by: Herve Codina Message-ID: <20260826083146.304291-2-herve.codina@bootlin.com> Signed-off-by: David Gibson --- fdtget.c | 71 ++++++++++++++++++-------------------------------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/fdtget.c b/fdtget.c index dd70985..c616969 100644 --- a/fdtget.c +++ b/fdtget.c @@ -138,21 +138,20 @@ static int show_data(struct display_info *disp, const char *data, int len) static int list_properties(const void *blob, int node) { const char *name; + const void *p; int prop; - prop = fdt_first_property_offset(blob, node); - do { - /* Stop silently when there are no more properties */ - if (prop < 0) - return prop == -FDT_ERR_NOTFOUND ? 0 : prop; - fdt_getprop_by_offset(blob, prop, &name, NULL); - if (name) + fdt_for_each_property_offset(prop, blob, node) { + p = fdt_getprop_by_offset(blob, prop, &name, NULL); + if (p && name) puts(name); - prop = fdt_next_property_offset(blob, prop); - } while (1); -} + } -#define MAX_LEVEL 32 /* how deeply nested we will go */ + if ((prop < 0) && (prop != -FDT_ERR_NOTFOUND)) + return prop; + + return 0; +} /** * List all subnodes in a node, one per line @@ -163,47 +162,21 @@ static int list_properties(const void *blob, int node) */ static int list_subnodes(const void *blob, int node) { - int nextoffset; /* next node offset from libfdt */ - uint32_t tag; /* current tag */ - int level = 0; /* keep track of nesting level */ const char *pathp; - int depth = 1; /* the assumed depth of this node */ + int subnode; - while (level >= 0) { - tag = fdt_next_tag(blob, node, &nextoffset); - switch (tag) { - case FDT_BEGIN_NODE: - pathp = fdt_get_name(blob, node, NULL); - if (level <= depth) { - if (pathp == NULL) - pathp = "/* NULL pointer error */"; - if (*pathp == '\0') - pathp = "/"; /* root is nameless */ - if (level == 1) - puts(pathp); - } - level++; - if (level >= MAX_LEVEL) { - printf("Nested too deep, aborting.\n"); - return 1; - } - break; - case FDT_END_NODE: - level--; - if (level == 0) - level = -1; /* exit the loop */ - break; - case FDT_END: - return 1; - case FDT_PROP: - break; - default: - if (level <= depth) - printf("Unknown tag 0x%08X\n", tag); - return 1; - } - node = nextoffset; + fdt_for_each_subnode(subnode, blob, node) { + pathp = fdt_get_name(blob, subnode, NULL); + if (pathp == NULL) + pathp = "/* NULL pointer error */"; + if (*pathp == '\0') + pathp = "/"; /* root is nameless */ + puts(pathp); } + + if (subnode < 0 && (subnode != -FDT_ERR_NOTFOUND)) + return subnode; + return 0; } From 7a1e017926004ecff5fce62d62d42ce9f3e00082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Or=C3=B3n=20Mart=C3=ADnez?= Date: Tue, 8 Sep 2026 12:07:27 +0200 Subject: [PATCH 6/7] libfdt: pass sources as keyword argument to library() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Meson expects the source list for library() to be passed either as positional arguments after the target name or via the 'sources:' keyword. Using the implicit positional form can lead to malformed linker commands on some toolchains, with spurious arguments such as 'libfdt' and '.' being treated as input files. Explicitly pass the source list via 'sources: sources' and declare the ABI version with 'soversion: '1'' to match the installed libfdt.so.1 soname. This fixes build failures where the linker reported: cannot find libfdt: file format not recognized cannot find .: file format not recognized Signed-off-by: Héctor Orón Martínez Message-ID: Signed-off-by: David Gibson --- libfdt/meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libfdt/meson.build b/libfdt/meson.build index 68d4c1d..6c41f9f 100644 --- a/libfdt/meson.build +++ b/libfdt/meson.build @@ -27,8 +27,10 @@ endif link_args += version_script libfdt = library( - 'fdt', sources, + 'fdt', + sources: sources, version: meson.project_version(), + soversion: '1', link_args: link_args, link_depends: 'version.lds', install: get_option('default_library') != 'static' or not wheel_only, From f3451d12532b9d382707c458a2ae2e5aa0e1eee4 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Tue, 6 Oct 2026 21:46:26 +1100 Subject: [PATCH 7/7] meson: Only take the version from dtc's own git repository When dtc is built from a release tarball unpacked inside another git repository, git describe finds that repository and its tag ends up in the version string, so dtc --version reports the tag rather than "DTC 1.8.1". Point git at dtc's own .git, so that vcs_tag falls back to the project version when there is none. Signed-off-by: Rudi Heitbaum Signed-off-by: David Gibson --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 501b706..520cd35 100644 --- a/meson.build +++ b/meson.build @@ -47,7 +47,7 @@ pylibfdt_enabled = not meson.is_cross_build() and py.found() and swig.found() ? wheel_only = get_option('wheel-only') version_gen_h = vcs_tag( - command: ['git', 'describe', '--dirty=+'], + command: ['git', '--git-dir=.git', 'describe', '--dirty=+'], input: 'version_gen.h.in', output: 'version_gen.h', )