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
