From: Chali Anis <[email protected]>

efi_load_fdt() never ran barebox's fixup/overlay pipeline on the
devicetree it handed to the kernel. When bootm.oftree was set, the
raw file bytes were extracted straight into EFI pages and installed
as-is via loadable_extract_into_buf_full(); of_unflatten_dtb(),
of_fix_tree() and of_flatten_dtb() were never called. When
bootm.oftree was unset, the function just returned early, so there
was no path at all for adopting (and fixing up) a devicetree already
exposed by firmware. Either way, none of barebox's usual fixups --
memory nodes, bootargs, state, overlays, and everything else hung
off of_register_fixup() -- ever reached the tree Linux booted with.

Fix this for both sources: add efi_fdt_find() to locate the FDT
firmware exposes via its EFI configuration table, and have
efi_load_fdt() fall back to it when no bootm.oftree is set. Whichever
tree is in play, unflatten it into data->of_root_node, run it through
the standard bootm_set_pending_oftree_overlays()/of_fix_tree()
sequence, reflatten it, and install the fixed-up result as the UEFI
configuration table -- matching what every other barebox boot path
already guarantees.

While here, size the FDT allocation/free from the actual flattened
tree instead of a fixed 2 MiB buffer, and reuse efi_fdt_find() in the
existing efi_fdt_probe() initcall so both callers share one
EFI-configuration-table lookup instead of duplicating it.

Assisted-by: Claude Sonnet 5 <[email protected]>
Signed-off-by: Chali Anis <[email protected]>
---
 efi/payload/bootm.c   | 60 ++++++++++++++++++++++++++++++-------
 efi/payload/fdt.c     | 34 ++++++++++++++-------
 efi/payload/init.c    | 69 +++++++++++++++++++++++++++++++++++++++++++
 include/efi/payload.h | 10 +++++++
 4 files changed, 152 insertions(+), 21 deletions(-)

diff --git a/efi/payload/bootm.c b/efi/payload/bootm.c
index 2f9cc3cbf76b..b9e1d9438e1c 100644
--- a/efi/payload/bootm.c
+++ b/efi/payload/bootm.c
@@ -111,27 +111,61 @@ static int efi_load_ramdisk(struct image_data *data,
 static int efi_load_fdt(struct image_data *data, void **fdt)
 {
        efi_physical_addr_t mem;
+       struct fdt_header *oftree;
+       bool is_loadable = true;
        efi_status_t efiret;
+       size_t size;
        void *vmem;
-       size_t bufsize = DIV_ROUND_UP(SZ_2M, EFI_PAGE_SIZE);
-       ssize_t ret;
+       int ret;
+
+       if (!data->oftree) {
+               /*
+                * No devicetree requested; fall back to the one provided by
+                * firmware (if any) so barebox's fixups still get applied to 
it.
+                */
+               oftree = efi_fdt_find(&size);
+               is_loadable = false;
+       } else {
+               oftree = loadable_extract(data->oftree, &size) ?: 
ERR_PTR(-ENODATA);
+               if (IS_ERR(oftree))
+                       pr_warn("Failed to extract oftree\n");
+       }
 
-       if (!data->oftree)
+       if (IS_ERR(oftree))
                return 0;
 
+       data->of_root_node = of_unflatten_dtb(oftree, size);
+       if (IS_ERR(data->of_root_node)) {
+               data->of_root_node = NULL;
+               pr_err("unable to unflatten devicetree\n");
+               return -EINVAL;
+       }
+
+       if (is_loadable)
+               free(oftree);
+
+       bootm_set_pending_oftree_overlays(data->oftree);
+       of_fix_tree(data->of_root_node);
+       bootm_clear_pending_oftree_overlays();
+
+       oftree = of_flatten_dtb(data->of_root_node);
+       if (!oftree)
+               return -EINVAL;
+
+       fdt_add_reserve_map(oftree);
+
+       size = efi_size_in_pages(fdt_totalsize(oftree) + 0x3000);
        efiret = BS->allocate_pages(EFI_ALLOCATE_ANY_PAGES, 
EFI_ACPI_RECLAIM_MEMORY,
-                                   bufsize, &mem);
+                                   size, &mem);
        if (EFI_ERROR(efiret)) {
                pr_err("Failed to allocate pages for FDT: %s\n", 
efi_strerror(efiret));
-               return -efi_errno(efiret);
+               ret = -efi_errno(efiret);
+               goto free_oftree;
        }
 
        vmem = efi_phys_to_virt(mem);
 
-       ret = loadable_extract_into_buf_full(data->oftree, vmem,
-                                            bufsize * EFI_PAGE_SIZE);
-       if (ret < 0)
-               goto free_efi_mem;
+       memcpy(vmem, oftree, fdt_totalsize(oftree));
 
        efiret = BS->install_configuration_table(&efi_fdt_guid, vmem);
        if (EFI_ERROR(efiret)) {
@@ -140,11 +174,14 @@ static int efi_load_fdt(struct image_data *data, void 
**fdt)
                goto free_efi_mem;
        }
 
+       free(oftree);
        *fdt = vmem;
        return 0;
 
 free_efi_mem:
-       BS->free_pages(mem, bufsize);
+       BS->free_pages(mem, size);
+free_oftree:
+       free(oftree);
        return ret;
 }
 
@@ -153,8 +190,9 @@ static void efi_unload_fdt(void *fdt)
        if (!fdt)
                return;
 
+       size_t size = efi_size_in_pages(fdt_totalsize(fdt) + 0x3000);
        BS->install_configuration_table(&efi_fdt_guid, NULL);
-       BS->free_pages(efi_virt_to_phys(fdt), DIV_ROUND_UP(SZ_2M, 
EFI_PAGE_SIZE));
+       BS->free_pages(efi_virt_to_phys(fdt), size);
 }
 
 static int do_bootm_efi_stub(struct image_data *data)
diff --git a/efi/payload/fdt.c b/efi/payload/fdt.c
index 9cdb32370f22..80e6a1fcec21 100644
--- a/efi/payload/fdt.c
+++ b/efi/payload/fdt.c
@@ -9,14 +9,14 @@
 #include <efi/payload/init.h>
 #include <efi/guid.h>
 
-static int efi_fdt_probe(void)
+void *efi_fdt_find(size_t *size)
 {
        struct efi_config_table *ect;
+       *size = 0;
 
        for_each_efi_config_table(ect) {
                struct fdt_header *oftree;
-               u32 magic, size;
-               int ret;
+               u32 magic;
 
                if (efi_guidcmp(ect->guid, EFI_DEVICE_TREE_GUID))
                        continue;
@@ -26,17 +26,31 @@ static int efi_fdt_probe(void)
 
                if (magic != FDT_MAGIC) {
                        pr_err("table has invalid magic 0x%08x\n", magic);
-                       return -EILSEQ;
+                       return ERR_PTR(-EILSEQ);
                }
 
-               size = be32_to_cpu(oftree->totalsize);
-               ret = write_file("/efi.dtb", oftree, size);
-               if (ret) {
-                       pr_err("error saving /efi.dtb: %pe\n", ERR_PTR(ret));
-                       return ret;
-               }
+               *size = fdt_totalsize(oftree);
+               return oftree;
+       }
+
+       pr_warn("No FDT found in EFI configuration tables\n");
+       return ERR_PTR(-ENODATA);
+}
 
+static int efi_fdt_probe(void)
+{
+       struct fdt_header *oftree;
+       size_t size;
+       int ret;
+
+       oftree = efi_fdt_find(&size);
+       if (IS_ERR(oftree) || !size)
                return 0;
+
+       ret = write_file("/efi.dtb", oftree, size);
+       if (ret) {
+               pr_err("error saving /efi.dtb: %pe\n", ERR_PTR(ret));
+               return ret;
        }
 
        return 0;
diff --git a/efi/payload/init.c b/efi/payload/init.c
index 6d2bd046e4c2..f0ce2a82cefc 100644
--- a/efi/payload/init.c
+++ b/efi/payload/init.c
@@ -203,6 +203,75 @@ static int efi_core_init(void)
 }
 core_efi_initcall(efi_core_init);
 
+/**
+ * efi_of_fixup_firmware_nodes() - import firmware-injected nodes onto @root
+ *
+ * A devicetree supplied out-of-band (e.g. embedded in a FIT image) was
+ * flattened at build time, so it can never carry nodes that firmware only
+ * adds to its own devicetree at runtime - most importantly the
+ * /reserved-memory carve-outs TF-A/OP-TEE add to mark their own
+ * secure-world memory off-limits (as child nodes, e.g. tegra-carveouts on
+ * Tegra), and the /firmware node describing the secure monitor call
+ * conduit. /firmware is copied wholesale, replacing whatever @root already
+ * had there. /reserved-memory is merged child by child instead, since
+ * @root may already carry its own reservations alongside firmware's:
+ * each child firmware's tree has is copied into @root's /reserved-memory
+ * (created if necessary), replacing any child of the same name @root
+ * already had.
+ */
+static int efi_of_fixup_firmware_nodes(struct device_node *root, void *unused)
+{
+       struct fdt_header *fw_oftree;
+       struct device_node *fw_root, *fw_firmware, *fw_resmem, *dst_resmem;
+       struct device_node *dst;
+       size_t fw_size;
+
+       fw_oftree = efi_fdt_find(&fw_size);
+       if (IS_ERR(fw_oftree))
+               return 0;
+
+       fw_root = of_unflatten_dtb(fw_oftree, fw_size);
+       if (IS_ERR(fw_root))
+               return 0;
+
+       fw_firmware = of_get_child_by_name(fw_root, "firmware");
+       if (fw_firmware) {
+               dst = of_get_child_by_name(root, "firmware");
+               if (dst)
+                       of_delete_node(dst);
+
+               of_copy_node(root, fw_firmware);
+       }
+
+       fw_resmem = of_get_child_by_name(fw_root, "reserved-memory");
+       if (fw_resmem) {
+               dst_resmem = of_get_child_by_name(root, "reserved-memory");
+               if (!dst_resmem) {
+                       of_copy_node(root, fw_resmem);
+               } else {
+                       struct device_node *child;
+
+                       for_each_child_of_node(fw_resmem, child) {
+                               dst = of_get_child_by_name(dst_resmem, 
child->name);
+                               if (dst)
+                                       of_delete_node(dst);
+
+                               of_copy_node(dst_resmem, child);
+                       }
+               }
+       }
+
+       of_delete_node(fw_root);
+
+       return 0;
+}
+
+static int efi_register_firmware_nodes_fixup(void)
+{
+       return of_register_fixup(efi_of_fixup_firmware_nodes, NULL);
+}
+core_efi_initcall(efi_register_firmware_nodes_fixup);
+
 /* Features of the loader, i.e. systemd-boot, barebox (imported from systemd) 
*/
 #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT          (1LL << 0)
 #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT (1LL << 1)
diff --git a/include/efi/payload.h b/include/efi/payload.h
index 381598ba59f2..8dc09dd5acdc 100644
--- a/include/efi/payload.h
+++ b/include/efi/payload.h
@@ -34,4 +34,14 @@ __attribute__((noreturn)) void efi_main(efi_handle_t, struct 
efi_system_table *)
             t - efi_sys_table->tables < efi_sys_table->nr_tables; \
             t++)
 
+#if IS_ENABLED(CONFIG_OFTREE)
+void *efi_fdt_find(size_t *size);
+#else
+static inline void *efi_fdt_find(size_t *size)
+{
+       *size = 0;
+       return NULL;
+}
+#endif
+
 #endif

Reply via email to