A payload that installs its own devicetree, like systemd-stub with a
UKI's .dtb or .dtbauto sections, replaces the one barebox installed and
with it every barebox fixup: machine compatible, serial number, OP-TEE
reservations, global overlays. systemd-stub asks the firmware to apply
them through EFI_DT_FIXUP_PROTOCOL, of which barebox only has the GUID.

Implement it on the root node:

- APPLY_FIXUPS runs of_fix_tree() on the payload's devicetree and
  returns EFI_BUFFER_TOO_SMALL with the required size if the result
  does not fit. The result is kept for the retry, so fixups and overlays
  run only once.
- RESERVE_MEMORY keeps boot-services allocations out of the memreserve
  entries and /reserved-memory nodes. The regions are requested as
  boot-services data, as the EFI loader cannot allocate
  EFI_RESERVED_TYPE; that suffices, because the kernel honours the
  devicetree reservations after ExitBootServices(). Regions barebox
  already holds, such as OP-TEE's, are skipped.
- INSTALL_TABLE installs the result as configuration table.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <[email protected]>
---
 efi/loader/protocols/Kconfig    |   9 ++
 efi/loader/protocols/Makefile   |   1 +
 efi/loader/protocols/dt_fixup.c | 185 ++++++++++++++++++++++++++++++++
 include/efi/protocol/dt_fixup.h |  21 ++++
 4 files changed, 216 insertions(+)
 create mode 100644 efi/loader/protocols/dt_fixup.c
 create mode 100644 include/efi/protocol/dt_fixup.h

diff --git a/efi/loader/protocols/Kconfig b/efi/loader/protocols/Kconfig
index ad7896065a..bc0d3adc8f 100644
--- a/efi/loader/protocols/Kconfig
+++ b/efi/loader/protocols/Kconfig
@@ -71,4 +71,13 @@ config EFI_LOADER_DEVICE_PATH_UTIL
          The device path utilities protocol creates and manipulates device
          paths and device nodes. It is required to run the EFI Shell.
 
+config EFI_LOADER_DT_FIXUP
+       bool "EFI_DT_FIXUP_PROTOCOL support"
+       depends on OFTREE
+       default y
+       help
+         Provide EFI_DT_FIXUP_PROTOCOL, so that an EFI payload installing its
+         own devicetree (like systemd-stub from a UKI's .dtb/.dtbauto section)
+         gets the barebox devicetree fixups and overlays applied to it.
+
 endmenu
diff --git a/efi/loader/protocols/Makefile b/efi/loader/protocols/Makefile
index d0b55bde46..ffc86ce84d 100644
--- a/efi/loader/protocols/Makefile
+++ b/efi/loader/protocols/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_EFI_LOADER_UNICODE_COLLATION_PROTOCOL2) += 
unicode_collation.o
 obj-$(CONFIG_EFI_LOADER_RNG) += rng.o
 obj-$(CONFIG_EFI_LOADER_DEVICE_PATH_UTIL) += device_path_utilities.o
 obj-$(CONFIG_EFI_LOADER_DEVICE_PATH_TO_TEXT) += device_path_to_text.o
+obj-$(CONFIG_EFI_LOADER_DT_FIXUP) += dt_fixup.o
diff --git a/efi/loader/protocols/dt_fixup.c b/efi/loader/protocols/dt_fixup.c
new file mode 100644
index 0000000000..fe48d4a160
--- /dev/null
+++ b/efi/loader/protocols/dt_fixup.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EFI_DT_FIXUP_PROTOCOL: apply barebox's fixups and overlays to a devicetree
+ * an EFI payload brings itself
+ */
+
+#define pr_fmt(fmt) "efi-loader: dt-fixup: " fmt
+
+#include <common.h>
+#include <init.h>
+#include <malloc.h>
+#include <memory.h>
+#include <of.h>
+#include <linux/libfdt.h>
+#include <efi/loader.h>
+#include <efi/memory.h>
+#include <crc.h>
+#include <linux/err.h>
+#include <efi/protocol/dt_fixup.h>
+#include <efi/loader/table.h>
+#include <efi/loader/trace.h>
+#include <efi/guid.h>
+#include <efi/error.h>
+
+static void dt_fixup_reserve(u64 addr, u64 size)
+{
+       u64 start = ALIGN_DOWN(addr, EFI_PAGE_SIZE);
+       u64 len = ALIGN(addr + size, EFI_PAGE_SIZE) - start;
+
+       /* only needs to hold until ExitBootServices() */
+       if (!request_sdram_region_silent("dt-reserved", start, len,
+                                        
efi_memory_type_to_resource_type(EFI_BOOT_SERVICES_DATA),
+                                        MEMATTRS_RW))
+               pr_debug("0x%llx+0x%llx already in use\n", addr, size);
+}
+
+/* Keep boot-services allocations out of what the devicetree reserves */
+static void dt_fixup_reserve_memory(const void *fdt)
+{
+       int i, node, sub, len, na, ns, entry;
+       u64 addr, size;
+
+       for (i = 0; i < fdt_num_mem_rsv(fdt); i++) {
+               if (!fdt_get_mem_rsv(fdt, i, &addr, &size) && size)
+                       dt_fixup_reserve(addr, size);
+       }
+
+       node = fdt_path_offset(fdt, "/reserved-memory");
+       if (node < 0)
+               return;
+
+       na = fdt_address_cells(fdt, node);
+       ns = fdt_size_cells(fdt, node);
+       if (na < 1 || na > 2 || ns < 1 || ns > 2) {
+               pr_warn("/reserved-memory: unsupported 
#address-cells/#size-cells\n");
+               return;
+       }
+       entry = (na + ns) * sizeof(fdt32_t);
+
+       fdt_for_each_subnode(sub, fdt, node) {
+               const fdt32_t *reg = fdt_getprop(fdt, sub, "reg", &len);
+
+               if (!reg)
+                       continue;
+
+               for (; len >= entry; len -= entry) {
+                       addr = of_read_number(reg, na);
+                       size = of_read_number(reg + na, ns);
+                       reg += na + ns;
+                       if (size)
+                               dt_fixup_reserve(addr, size);
+               }
+       }
+}
+
+/* Kept for a caller retrying with a larger buffer, so fixups run only once */
+static struct {
+       void *fixed;
+       size_t in_len;
+       u32 in_crc;
+} dt_fixup_pending;
+
+static void *dt_fixup_apply(const void *dtb)
+{
+       size_t len = fdt_totalsize(dtb);
+       u32 crc = crc32(0, dtb, len);
+       struct device_node *root;
+       void *fixed;
+
+       if (dt_fixup_pending.fixed && dt_fixup_pending.in_len == len &&
+           dt_fixup_pending.in_crc == crc) {
+               fixed = dt_fixup_pending.fixed;
+               dt_fixup_pending.fixed = NULL;
+               return fixed;
+       }
+
+       root = of_unflatten_dtb(dtb, len);
+       if (IS_ERR(root))
+               return ERR_CAST(root);
+
+       of_fix_tree(root);
+
+       fixed = of_flatten_dtb(root);
+       of_delete_node(root);
+       if (!fixed)
+               return ERR_PTR(-ENOMEM);
+
+       fdt_add_reserve_map(fixed);
+
+       free(dt_fixup_pending.fixed);
+       dt_fixup_pending.fixed = NULL;
+       dt_fixup_pending.in_len = len;
+       dt_fixup_pending.in_crc = crc;
+
+       return fixed;
+}
+
+static efi_status_t EFIAPI dt_fixup(struct efi_dt_fixup_protocol *this,
+                                   void *dtb, size_t *buffer_size, u32 flags)
+{
+       efi_status_t ret = EFI_SUCCESS;
+       void *fixed = NULL;
+       size_t size;
+
+       EFI_ENTRY("%p, %p, %p, %u", this, dtb, buffer_size, flags);
+
+       if (!this || !dtb || !buffer_size || !flags || (flags & ~EFI_DT_ALL)) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       if (fdt_check_header(dtb)) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       if (flags & EFI_DT_APPLY_FIXUPS) {
+               fixed = dt_fixup_apply(dtb);
+               if (IS_ERR(fixed)) {
+                       ret = PTR_ERR(fixed) == -ENOMEM ? EFI_OUT_OF_RESOURCES :
+                                                         EFI_INVALID_PARAMETER;
+                       fixed = NULL;
+                       goto out;
+               }
+
+               size = fdt_totalsize(fixed);
+               if (size > *buffer_size) {
+                       /* the caller retries with a buffer of this size */
+                       *buffer_size = size;
+                       dt_fixup_pending.fixed = fixed;
+                       fixed = NULL;
+                       ret = EFI_BUFFER_TOO_SMALL;
+                       goto out;
+               }
+
+               memcpy(dtb, fixed, size);
+       } else if (fdt_totalsize(dtb) > *buffer_size) {
+               *buffer_size = fdt_totalsize(dtb);
+               ret = EFI_BUFFER_TOO_SMALL;
+               goto out;
+       }
+
+       if (flags & EFI_DT_RESERVE_MEMORY)
+               dt_fixup_reserve_memory(dtb);
+
+       if (flags & EFI_DT_INSTALL_TABLE)
+               ret = efi_install_configuration_table(&efi_fdt_guid, dtb);
+
+out:
+       free(fixed);
+       return EFI_EXIT(ret);
+}
+
+static struct efi_dt_fixup_protocol efi_dt_fixup_prot = {
+       .revision = EFI_DT_FIXUP_PROTOCOL_REVISION,
+       .fixup = dt_fixup,
+};
+
+static int efi_dt_fixup_init(void)
+{
+       efi_add_root_node_protocol_deferred(&efi_dt_fixup_protocol_guid,
+                                           &efi_dt_fixup_prot);
+       return 0;
+}
+device_initcall(efi_dt_fixup_init);
diff --git a/include/efi/protocol/dt_fixup.h b/include/efi/protocol/dt_fixup.h
new file mode 100644
index 0000000000..fd418793f6
--- /dev/null
+++ b/include/efi/protocol/dt_fixup.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __EFI_PROTOCOL_DT_FIXUP_H_
+#define __EFI_PROTOCOL_DT_FIXUP_H_
+
+#include <efi/types.h>
+
+#define EFI_DT_FIXUP_PROTOCOL_REVISION 0x00010000
+
+#define EFI_DT_APPLY_FIXUPS    0x00000001
+#define EFI_DT_RESERVE_MEMORY  0x00000002
+#define EFI_DT_INSTALL_TABLE   0x00000004
+#define EFI_DT_ALL             (EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY | \
+                                EFI_DT_INSTALL_TABLE)
+
+struct efi_dt_fixup_protocol {
+       u64 revision;
+       efi_status_t (EFIAPI *fixup)(struct efi_dt_fixup_protocol *this,
+                                    void *dtb, size_t *buffer_size, u32 flags);
+};
+
+#endif
-- 
2.43.0


Reply via email to