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
Signed-off-by: Chali Anis <[email protected]>
---
 efi/payload/bootm.c   | 58 +++++++++++++++++++++++++++++++++++--------
 efi/payload/fdt.c     | 34 +++++++++++++++++--------
 include/efi/payload.h | 10 ++++++++
 3 files changed, 81 insertions(+), 21 deletions(-)

diff --git a/efi/payload/bootm.c b/efi/payload/bootm.c
index 2f9cc3cbf76b..094b2e9c6466 100644
--- a/efi/payload/bootm.c
+++ b/efi/payload/bootm.c
@@ -111,27 +111,59 @@ 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;
+
+       size = DIV_ROUND_UP(fdt_totalsize(oftree), EFI_PAGE_SIZE);
        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 +172,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 +188,9 @@ static void efi_unload_fdt(void *fdt)
        if (!fdt)
                return;
 
+       size_t size = DIV_ROUND_UP(fdt_totalsize(fdt), EFI_PAGE_SIZE);
        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/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