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

Reply via email to