[PATCH v2 06/10] binman: Add support for pre-patching FDTs in a FIT with a /chosen node

Alexey Charkov <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
When a generated FIT is used to boot Linux directly, bypassing U-Boot
proper (Falcon mode), there is no runtime code to discover the kernel
command line and (optional) initrd location and include them in a FDT.

To facilitate easier preparation of a ready-to-boot FIT, add support for
pre-patching the FDTs in a FIT with a preconfigured /chosen node including
the bootargs and initrd location.

Signed-off-by: Alexey Charkov <[email protected]>
---
 tools/binman/entry.py     |   8 ++++
 tools/binman/etype/fit.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 111 insertions(+)

diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index ce7ef28e94b1..e0dea8bf3a49 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -636,6 +636,14 @@ class Entry(object):
         self.Detail('GetData: size %s' % to_hex_size(self.data))
         return self.data
 
+    def GetNode(self):
+        """Get the devicetree node which describes this entry
+
+        Returns:
+            Node: Node for this entry
+        """
+        return self._node
+
     def GetPaddedData(self, data=None):
         """Get the data for an entry including any padding
 
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 32caa03a7e16..750ff5b47ed5 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -304,6 +304,40 @@ class Entry_fit(Entry_section):
 
     See :ref:`fdtgrep_filter` for more information.
 
+    Patching /chosen into the generated device trees
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    When the OS is booted directly from this FIT (e.g. Falcon mode) U-Boot
+    proper never runs, so its usual runtime /chosen fixups do not happen. Two
+    optional properties on the `@fdt-SEQ` node make binman patch each generated
+    device tree at build time:
+
+    fit,bootargs
+        A string written to /chosen/bootargs of every generated device tree
+        (any existing value is overwritten). Typically set to `CONFIG_BOOTARGS`
+        via the preprocessed devicetree source.
+
+    fit,initrd
+        The image name of a loadable (e.g. "ramdisk") to use as the initramfs.
+        Its `load` address and packed size are written to
+        /chosen/linux,initrd-start and /chosen/linux,initrd-end (at the width of
+        the device tree's root #address-cells) and reserved in the FDT
+        memory-reservation map, mirroring fdt_initrd(). If the referenced image
+        is absent or empty (e.g. an optional initramfs that was not supplied)
+        the initrd properties are omitted.
+
+    For example::
+
+        images {
+            @fdt-SEQ {
+                description = "fdt-NAME";
+                type = "flat_dt";
+                compression = "none";
+                fit,bootargs = "console=ttyS2,1500000 root=/dev/mmcblk0p2";
+                fit,initrd = "ramdisk";
+            };
+        };
+
     Generating nodes from an ELF file (split-elf)
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -749,6 +783,66 @@ class Entry_fit(Entry_section):
         return self.fdtgrep.create_for_phase(infile, phase, outfile,
                                              self._remove_props)
 
+    def _patch_fdt_chosen(self, data, bootargs, initrd_name):
+        """Patch /chosen (bootargs and initrd) into an embedded FDT
+
+        This writes a kernel command line and/or initramfs location into the
+        /chosen node of a device tree that binman is about to embed in the FIT.
+        It is used for Falcon-mode images, where U-Boot proper never runs to
+        perform these fixups itself.
+
+        Args:
+            data (bytes): Device-tree contents to patch
+            bootargs (str or None): Command line to store in /chosen/bootargs,
+                or None to leave the command line unchanged
+            initrd_name (str or None): Image name of the loadable to use as the
+                initramfs. Its 'load' address and packed size are written to
+                /chosen/linux,initrd-start and /chosen/linux,initrd-end (at the
+                width of the DTB's root #address-cells) and reserved in the FDT
+                memory-reservation map, mirroring fdt_initrd(). Ignored when the
+                referenced image is absent or empty, or when None.
+
+        Returns:
+            bytes: The patched device-tree contents
+
+        Raises:
+            ValueError: the referenced image has data but no 'load' address
+        """
+        fdt = libfdt.Fdt(bytearray(data))
+        fdt.resize(fdt.totalsize() + 1024 + (len(bootargs) if bootargs else 0))
+
+        chosen = fdt.path_offset('/chosen', libfdt.QUIET_NOTFOUND)
+        if chosen == -libfdt.FDT_ERR_NOTFOUND:
+            chosen = fdt.add_subnode(0, 'chosen')
+
+        if bootargs:
+            fdt.setprop_str(chosen, 'bootargs', bootargs)
+
+        if initrd_name:
+            entry = self._priv_entries.get(initrd_name)
+            initrd = entry.GetData(required=False) if entry else None
+            size = len(initrd) if initrd else 0
+            if size:
+                start = fdt_util.GetInt(entry.GetNode(), 'load')
+                if start is None:
+                    self.Raise(f"fit,initrd image '{initrd_name}' has no "
+                               "'load' address")
+                end = start + size
+                # Use the device tree's root #address-cells for the width, as
+                # fdt_initrd() does (4 bytes per cell)
+                cells = 4 * libfdt.check_err(
+                    libfdt.fdt_address_cells(fdt._fdt, 0))
+                fdt.setprop(chosen, 'linux,initrd-start',
+                            start.to_bytes(cells, 'big'))
+                fdt.setprop(chosen, 'linux,initrd-end',
+                            end.to_bytes(cells, 'big'))
+                # Reserve the initramfs region so the kernel does not reuse it
+                libfdt.check_err(
+                    libfdt.fdt_add_mem_rsv(fdt._fdt, start, size))
+
+        fdt.pack()
+        return bytes(fdt.as_bytearray()[:fdt.totalsize()])
+
     def _build_input(self):
         """Finish the FIT by adding the 'data' properties to it
 
@@ -857,6 +951,8 @@ class Entry_fit(Entry_section):
                     else:
                         fname = tools.get_input_filename(fdt_fname + '.dtb')
                     fdt_phase = None
+                    bootargs = None
+                    initrd_name = None
                     with fsw.add_node(node_name):
                         for pname, prop in node.props.items():
                             if pname == 'fit,firmware':
@@ -876,6 +972,10 @@ class Entry_fit(Entry_section):
                                 fsw.property('compatible', prop.bytes)
                             elif pname == 'fit,fdt-phase':
                                 fdt_phase = fdt_util.GetString(node, pname)
+                            elif pname == 'fit,bootargs':
+                                bootargs = fdt_util.GetString(node, pname)
+                            elif pname == 'fit,initrd':
+                                initrd_name = fdt_util.GetString(node, pname)
                             elif pname.startswith('fit,'):
                                 self._raise_subnode(
                                     node, f"Unknown directive '{pname}'")
@@ -896,6 +996,9 @@ class Entry_fit(Entry_section):
                                 data = tools.read_file(phase_fname)
                             else:
                                 data = tools.read_file(fname)
+                            if bootargs is not None or initrd_name is not None:
+                                data = self._patch_fdt_chosen(data, bootargs,
+                                                              initrd_name)
                             fsw.property('data', data)
 
                         for subnode in node.subnodes:

-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.