* [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
@ 2026-09-03 13:09 Carlo Caione
2026-09-03 13:09 ` [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree Carlo Caione
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
EBBR-style firmware owns the devicetree: the OS is booted via UEFI and
receives the devicetree from the firmware through the EFI configuration
table instead of shipping its own. U-Boot has the handoff mechanism
(efi_install_fdt()), but no generic way to describe and load the
firmware-owned source. As a result, platforms carry downstream commands
to provide it.
This series adds a firmware-FDT uclass with storage-specific source
drivers and common FIT selection, verification, overlay assembly and
caching:
- one FIT ("fdt.itb" by default) carries the base devicetree and its
overlays. FIT configurations describe valid combinations, so a signed
configuration authenticates the base, overlay set and ordering;
- the first backend, selected by a standalone
'u-boot,firmware-fdt-block' control-DT node, reads the FIT from a
filesystem on a GPT partition. The compatible suffix leaves room for
drivers which obtain the same FIT from other firmware storage;
- 'fw_fdt_part' can override the partition for A/B firmware selection,
while 'fw_fdt_config' can select the configuration for the current boot
policy. Otherwise compatible best-match against the control devicetree
is used, falling back to the FIT default;
- the source device caches the assembled result by those two selectors,
avoiding repeated reads and verification while the EFI bootmeth scans
bootflow candidates;
- once a source is configured, any failure to assemble the devicetree is
fatal rather than silently falling back to an unverified source. Every
image is verified up front so a corrupt overlay cannot be silently
skipped.
The loader does not depend on standard boot. EFI is its first consumer,
but the source drivers, FIT handling and staging interface are generic.
Both the EFI bootmeth and EFI boot manager use the same staging helper.
Patch 1 makes an FDT passed to efi_bootmgr_run() outrank a Boot#### FDT,
as an independent behaviour fix. Patch 2 adds the uclass, block backend,
documentation and staging interface. Patches 3 and 4 integrate the EFI
bootmeth and boot manager. Patch 5 adds sandbox coverage.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
Changes in v3:
- Model firmware-FDT sources as a uclass with storage backend drivers;
split out the block backend and reject multiple enabled sources.
- Cache the verified, assembled devicetree by the partition and
configuration runtime selectors.
- Rename efi_stage_firmware_fdt() to the generic firmware_fdt_stage().
- Add separate configurable size limits for the input FIT and assembled
devicetree.
- Validate the assembled devicetree before publishing it and avoid
duplicating the internal FIT filename.
- Keep EFI and firmware-FDT sizes separate in the boot manager and retain
the FIT filename for failure diagnostics.
- Extend sandbox coverage for caching and selector invalidation, a missing
FIT and multiple configured sources.
- Use the current SystemReady DeviceTree name.
- Clarify fw_fdt_part as the A/B partition override and fw_fdt_config as
the per-boot-target configuration override accepted in the v2 review.
- Add Reviewed-by tags from Ilias Apalodimas and Simon Glass to patch 1.
- Link to v2: https://patch.msgid.link/20260728-ccaione-upstream-ebbr-v2-0-4efb529140d6@baylibre.com
Changes in v2:
- Make the loader independent of bootstd and describe EFI as its first
consumer.
- Describe the source with a standalone compatible node which points to
the backing media through a phandle.
- Rename 'boot_dtb' to 'fw_fdt_part' and document both environment
variables in doc/usage/environment.rst.
- Factor EFI staging and fail-closed policy into one helper used by all
three call sites.
- Track the assembled FDT's ownership explicitly and own the FIT filename.
- Select FIT_BEST_MATCH and add explicit compatible-selection coverage.
- Split the passed-FDT precedence change into its own first patch.
- Build the entire sandbox source topology at runtime, store mmc11.img in
persistent_data_dir and add staging, configuration-chaining and corrupt
base/overlay fail-closed coverage.
- Link to v1: https://patch.msgid.link/20260706-ccaione-upstream-ebbr-v1-0-e742cdd6abe5@baylibre.com
---
Carlo Caione (5):
efi_loader: bootmgr: preserve a passed devicetree
boot: add a firmware-owned devicetree source
bootmeth: efi: use the firmware-owned devicetree
efi_loader: bootmgr: install the firmware-owned devicetree
test: boot: add firmware-FDT source tests
MAINTAINERS | 3 +
boot/Kconfig | 49 +++
boot/Makefile | 2 +
boot/bootmeth_efi.c | 45 ++-
boot/firmware_fdt.c | 303 ++++++++++++++++++
boot/firmware_fdt_block.c | 143 +++++++++
boot/image-fdt.c | 3 +-
boot/image-fit.c | 9 +-
configs/sandbox_defconfig | 1 +
doc/develop/uefi/firmware_fdt.rst | 118 +++++++
doc/develop/uefi/index.rst | 1 +
doc/device-tree-bindings/firmware-fdt.txt | 160 ++++++++++
doc/usage/environment.rst | 13 +
include/dm/uclass-id.h | 1 +
include/firmware_fdt.h | 106 +++++++
include/image.h | 5 +-
lib/efi_loader/efi_bootmgr.c | 45 ++-
test/boot/Makefile | 1 +
test/boot/firmware_fdt.c | 498 ++++++++++++++++++++++++++++++
test/py/tests/test_ut.py | 183 +++++++++++
20 files changed, 1670 insertions(+), 19 deletions(-)
---
base-commit: 3f19667468fb93bcb5702d23aa057b72639e3ac8
change-id: 20260706-ccaione-upstream-ebbr-206c30be3a3a
Best regards,
--
Carlo Caione <ccaione@baylibre.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
@ 2026-09-03 13:09 ` Carlo Caione
2026-09-03 13:09 ` [PATCH v3 2/5] boot: add a firmware-owned devicetree source Carlo Caione
` (3 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
An FDT explicitly passed to efi_bootmgr_run() is currently replaced by
the FDT referenced by the selected Boot#### load option. This makes an
operator-supplied devicetree ineffective whenever the boot option carries
one.
Only consult the load-option FDT when no FDT was passed. Document the
resulting precedence so the explicit call argument remains the
highest-priority source.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
lib/efi_loader/efi_bootmgr.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
index 8938b214ceb..6b5dad21b06 100644
--- a/lib/efi_loader/efi_bootmgr.c
+++ b/lib/efi_loader/efi_bootmgr.c
@@ -1294,9 +1294,9 @@ out:
* efi_bootmgr_run() - execute EFI boot manager
* @fdt: Flat device tree
*
- * Invoke EFI boot manager and execute a binary depending on
- * boot options. If @fdt is not NULL, it will be passed to
- * the executed binary.
+ * Invoke the EFI boot manager and execute a binary according to its boot
+ * options. The devicetree precedence, from highest to lowest, is an FDT
+ * passed in @fdt, the Boot#### load-option FDT, then the distro/ESP FDT.
*
* Return: status code
*/
@@ -1305,7 +1305,7 @@ efi_status_t efi_bootmgr_run(void *fdt)
efi_handle_t handle;
void *load_options;
efi_status_t ret;
- void *fdt_lo, *fdt_distro = NULL;
+ void *fdt_lo = NULL, *fdt_distro = NULL;
efi_uintn_t fdt_size;
/* Initialize EFI drivers */
@@ -1320,11 +1320,13 @@ efi_status_t efi_bootmgr_run(void *fdt)
}
if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
- ret = load_fdt_from_load_option(&fdt_lo);
- if (ret != EFI_SUCCESS)
- return ret;
- if (fdt_lo)
- fdt = fdt_lo;
+ if (!fdt) {
+ ret = load_fdt_from_load_option(&fdt_lo);
+ if (ret != EFI_SUCCESS)
+ return ret;
+ if (fdt_lo)
+ fdt = fdt_lo;
+ }
if (!fdt) {
efi_load_distro_fdt(handle, &fdt_distro, &fdt_size);
fdt = fdt_distro;
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 2/5] boot: add a firmware-owned devicetree source
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree Carlo Caione
@ 2026-09-03 13:09 ` Carlo Caione
2026-09-03 13:09 ` [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree Carlo Caione
` (2 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
Platforms following EBBR / Arm SystemReady DeviceTree keep the
devicetree in firmware-owned storage, updated independently of the
operating system, rather than shipping it in the OS image. U-Boot has no
generic way to source and assemble such a devicetree.
Add a firmware-FDT uclass which loads a FIT through a storage backend,
selects and verifies one configuration, and assembles its base DTB and
overlays. Each backend supplies the FIT while selection, verification,
assembly, caching and consumers remain storage-independent. Require
exactly one enabled source device.
Implement the first backend, 'u-boot,firmware-fdt-block', which reads the
FIT from a filesystem on a GPT partition of a block device:
firmware-fdt {
compatible = "u-boot,firmware-fdt-block";
firmware-fdt-store = <&mmc0>;
partition-type-uuid =
"384e979b-eb76-435a-a3a6-1a071dbad91d";
partition-name = "firmware";
filename = "fdt.itb";
};
The 'firmware-fdt-store' phandle points to the media device, while the
GPT type UUID and/or name select the partition. 'fw_fdt_part' is a
runtime A/B partition override. 'fw_fdt_config' is a runtime
configuration override for boot policy; otherwise use compatible
best-match against the control devicetree and fall back to the FIT
default.
The compatible suffix identifies the backend. Future drivers can load
the same fdt.itb from other firmware storage, such as UBI on MTD,
without changing the FIT contract or common code.
Cache the assembled result in the source device, keyed by both runtime
selectors, so repeated bootflow candidates do not read and verify it
again. Add separate Kconfig limits for the input FIT and assembled FDT.
The loader is independent of bootstd. Add firmware_fdt_stage() as a
generic staging helper for its initial EFI consumers. Make -ENOENT mean
only "no source configured"; once a source exists, a missing partition,
FIT or configuration is fatal. Reject load addresses, external data and
configuration chaining, require every image to be a flat devicetree, and
verify every image before assembly.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
MAINTAINERS | 3 +
boot/Kconfig | 49 +++++
boot/Makefile | 2 +
boot/firmware_fdt.c | 303 ++++++++++++++++++++++++++++++
boot/firmware_fdt_block.c | 143 ++++++++++++++
boot/image-fdt.c | 3 +-
boot/image-fit.c | 9 +-
doc/develop/uefi/firmware_fdt.rst | 118 ++++++++++++
doc/develop/uefi/index.rst | 1 +
doc/device-tree-bindings/firmware-fdt.txt | 160 ++++++++++++++++
doc/usage/environment.rst | 13 ++
include/dm/uclass-id.h | 1 +
include/firmware_fdt.h | 106 +++++++++++
include/image.h | 5 +-
14 files changed, 912 insertions(+), 4 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index e41ff7700df..c8d0c8c9654 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -902,8 +902,10 @@ F: boot/bootdev*.c
F: boot/bootflow.c
F: boot/bootmeth*.c
F: boot/bootstd.c
+F: boot/firmware_fdt*.c
F: cmd/bootdev.c
F: cmd/bootflow.c
+F: doc/device-tree-bindings/firmware-fdt.txt
F: doc/develop/bootstd/
F: doc/usage/bootdev.rst
F: doc/usage/bootflow.rst
@@ -913,6 +915,7 @@ F: include/bootdev.h
F: include/bootflow.h
F: include/bootmeth.h
F: include/bootstd.h
+F: include/firmware_fdt.h
F: net/eth_bootdevice.c
F: test/boot/
diff --git a/boot/Kconfig b/boot/Kconfig
index c67dc0ba493..e51094fdd25 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -194,6 +194,55 @@ config FIT_BEST_MATCH
If several configurations match equally well, the one named by
the configurations node 'default' property is preferred.
+config FIRMWARE_FDT
+ bool "Source the devicetree from firmware-owned storage"
+ depends on DM && FIT && OF_CONTROL
+ select FIT_BEST_MATCH
+ select OF_LIBFDT
+ select OF_LIBFDT_OVERLAY
+ help
+ Source the devicetree from firmware-owned storage rather than from
+ the operating-system image. The source carries a FIT whose
+ images hold the base DTB and its overlays, and whose configurations
+ name the bootable combinations. The assembled devicetree can be
+ handed to the OS, so it can be updated as part of the firmware,
+ independently of the operating system.
+
+ For secure boot, sign the FIT configurations and enable
+ FIT_SIGNATURE with a required key in the control devicetree; the
+ standard verified-boot policy then rejects unsigned FITs.
+
+ Storage-specific drivers provide the FIT to the common selection,
+ verification and assembly code. This is intended for platforms
+ following EBBR / Arm SystemReady DeviceTree. Say N unless you are
+ booting such a platform.
+
+config FIRMWARE_FDT_BLOCK
+ bool "Block-device firmware-owned devicetree backend"
+ depends on FIRMWARE_FDT && BLK
+ default y
+ select EFI_PARTITION
+ select PARTITION_TYPE_GUID
+ help
+ Read the firmware-owned FIT from a filesystem on a GPT partition of
+ a block device. Other backends can provide the same FIT contract from
+ different storage without changing its consumers.
+
+config FIRMWARE_FDT_FIT_MAX_SIZE
+ hex "Maximum firmware-owned FIT size"
+ depends on FIRMWARE_FDT
+ default 0x400000
+ help
+ Maximum accepted size in bytes for the FIT read by a storage backend.
+
+config FIRMWARE_FDT_MAX_SIZE
+ hex "Maximum assembled firmware devicetree size"
+ depends on FIRMWARE_FDT
+ default 0x400000
+ help
+ Maximum accepted size in bytes for the devicetree after applying the
+ selected FIT configuration's overlays.
+
config FIT_IMAGE_POST_PROCESS
bool "Enable post-processing of FIT artifacts after loading by U-Boot"
depends on SOCFPGA_SECURE_VAB_AUTH
diff --git a/boot/Makefile b/boot/Makefile
index 7fb56e7ef37..487141b963a 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -27,6 +27,8 @@ obj-$(CONFIG_$(PHASE_)BOOTSTD) += bootstd-uclass.o
obj-$(CONFIG_$(PHASE_)BOOTSTD_MENU) += bootflow_menu.o
obj-$(CONFIG_$(PHASE_)BOOTSTD_PROG) += prog_boot.o
+obj-$(CONFIG_$(PHASE_)FIRMWARE_FDT) += firmware_fdt.o
+obj-$(CONFIG_$(PHASE_)FIRMWARE_FDT_BLOCK) += firmware_fdt_block.o
obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX) += bootmeth_extlinux.o
obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += bootmeth_pxe.o
diff --git a/boot/firmware_fdt.c b/boot/firmware_fdt.c
new file mode 100644
index 00000000000..9d2303b5747
--- /dev/null
+++ b/boot/firmware_fdt.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#define LOG_CATEGORY UCLASS_FIRMWARE_FDT
+
+#include <dm.h>
+#include <env.h>
+#include <firmware_fdt.h>
+#include <image.h>
+#include <log.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <dm/device-internal.h>
+#include <dm/uclass.h>
+#include <linux/libfdt.h>
+#include <linux/string.h>
+
+#define FW_FDT_FILENAME "fdt.itb"
+
+struct firmware_fdt_priv {
+ struct firmware_fdt cache;
+ char *part;
+ char *config;
+ bool cache_valid;
+};
+
+static const char *fw_fdt_selector(const char *name)
+{
+ const char *value = env_get(name);
+
+ return value && *value ? value : NULL;
+}
+
+static bool fw_fdt_streq(const char *left, const char *right)
+{
+ if (!left || !right)
+ return left == right;
+
+ return !strcmp(left, right);
+}
+
+void firmware_fdt_free(struct firmware_fdt *fw)
+{
+ if (!fw->borrowed) {
+ if (fw->fdt_owned)
+ free(fw->fdt);
+ free(fw->fit);
+ }
+
+ memset(fw, '\0', sizeof(*fw));
+}
+
+static void fw_fdt_clear_cache(struct firmware_fdt_priv *priv)
+{
+ firmware_fdt_free(&priv->cache);
+ free(priv->part);
+ free(priv->config);
+ memset(priv, '\0', sizeof(*priv));
+}
+
+static int fw_fdt_get_source(struct udevice **devp)
+{
+ struct udevice *dev, *source = NULL;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_FIRMWARE_FDT, &uc);
+ if (ret)
+ return ret;
+
+ uclass_foreach_dev(dev, uc) {
+ if (source)
+ return log_msg_ret("multi", -EINVAL);
+ source = dev;
+ }
+
+ if (!source)
+ return -ENOENT;
+
+ ret = device_probe(source);
+ if (ret)
+ return ret == -ENOENT ? -ENODEV : ret;
+
+ *devp = source;
+
+ return 0;
+}
+
+static int fw_fdt_read_fit(struct udevice *dev, const char *part,
+ const char *filename, void **fitp, ulong *sizep)
+{
+ const struct firmware_fdt_ops *ops = device_get_ops(dev);
+
+ if (!ops || !ops->read_fit)
+ return -ENOSYS;
+
+ return ops->read_fit(dev, part, filename, fitp, sizep);
+}
+
+static int fw_fdt_check_images(const void *fit)
+{
+ int images, node;
+
+ images = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images < 0)
+ return log_msg_ret("img", -EINVAL);
+
+ fdt_for_each_subnode(node, fit, images) {
+ if (!fit_image_check_type(fit, node, IH_TYPE_FLATDT))
+ return log_msg_ret("type", -EINVAL);
+
+ if (fdt_getprop(fit, node, FIT_LOAD_PROP, NULL))
+ return log_msg_ret("load", -EINVAL);
+
+ if (fdt_getprop(fit, node, FIT_DATA_OFFSET_PROP, NULL) ||
+ fdt_getprop(fit, node, FIT_DATA_POSITION_PROP, NULL))
+ return log_msg_ret("ext", -EINVAL);
+ }
+
+ return 0;
+}
+
+static int fw_fdt_assemble(struct udevice *dev, const char *part,
+ const char *config, struct firmware_fdt *out)
+{
+ struct bootm_headers images;
+ const char *filename, *conf = config;
+ bool fdt_owned = false;
+ ulong data, len;
+ void *fdt;
+ int ret;
+
+ memset(&images, '\0', sizeof(images));
+ images.verify = 1;
+
+ filename = dev_read_string(dev, "filename");
+ if (!filename)
+ filename = FW_FDT_FILENAME;
+
+ ret = fw_fdt_read_fit(dev, part, filename, &out->fit, &out->fit_size);
+ if (ret)
+ return ret;
+
+ ret = fit_check_format(out->fit, out->fit_size);
+ if (ret)
+ return log_msg_ret("fit", -EINVAL);
+
+ ret = fw_fdt_check_images(out->fit);
+ if (ret)
+ return ret;
+
+ /* Verify overlays too, since boot_get_fdt_fit() can skip a bad one. */
+ if (!fit_all_image_verify(out->fit))
+ return log_msg_ret("verify", -EACCES);
+
+ if (conf && strchr(conf, '#'))
+ return log_msg_ret("chain", -EINVAL);
+
+ ret = boot_get_fdt_fit(&images, map_to_sysmem(out->fit), NULL, &conf,
+ IH_ARCH_DEFAULT, &data, &len, &fdt_owned);
+ if (ret < 0)
+ return log_msg_ret("conf", ret);
+
+ fdt = map_sysmem(data, len);
+ if (len > CONFIG_FIRMWARE_FDT_MAX_SIZE) {
+ ret = -E2BIG;
+ goto err_fdt;
+ }
+
+ ret = fdt_check_full(fdt, len);
+ if (ret) {
+ ret = -EINVAL;
+ goto err_fdt;
+ }
+
+ out->fdt = fdt;
+ out->size = len;
+ out->name = filename;
+ out->fdt_owned = fdt_owned;
+
+ return 0;
+
+err_fdt:
+ if (fdt_owned)
+ free(fdt);
+
+ return log_msg_ret("fdt", ret);
+}
+
+static int fw_fdt_load_source(struct udevice *dev, struct firmware_fdt *out)
+{
+ struct firmware_fdt_priv *priv = dev_get_uclass_priv(dev);
+ const char *part = fw_fdt_selector("fw_fdt_part");
+ const char *config = fw_fdt_selector("fw_fdt_config");
+ char *part_copy = NULL, *config_copy = NULL;
+ int ret;
+
+ if (priv->cache_valid && fw_fdt_streq(part, priv->part) &&
+ fw_fdt_streq(config, priv->config))
+ goto cached;
+
+ if (part) {
+ part_copy = strdup(part);
+ if (!part_copy)
+ return -ENOMEM;
+ }
+ if (config) {
+ config_copy = strdup(config);
+ if (!config_copy) {
+ free(part_copy);
+ return -ENOMEM;
+ }
+ }
+
+ fw_fdt_clear_cache(priv);
+ priv->part = part_copy;
+ priv->config = config_copy;
+
+ ret = fw_fdt_assemble(dev, priv->part, priv->config, &priv->cache);
+ if (ret) {
+ fw_fdt_clear_cache(priv);
+ if (ret == -ENOENT)
+ ret = -ENODEV;
+ return ret;
+ }
+ priv->cache_valid = true;
+
+cached:
+ *out = priv->cache;
+ out->borrowed = true;
+
+ return 0;
+}
+
+int firmware_fdt_load(struct firmware_fdt *out)
+{
+ struct udevice *dev;
+ int ret;
+
+ memset(out, '\0', sizeof(*out));
+
+ ret = fw_fdt_get_source(&dev);
+ if (ret)
+ return ret;
+
+ return fw_fdt_load_source(dev, out);
+}
+
+int firmware_fdt_stage(ulong fdt_addr, ulong *fdt_sizep, char **namep)
+{
+ struct firmware_fdt fw;
+ struct udevice *dev;
+ const char *filename;
+ char *name = NULL;
+ int ret;
+
+ if (namep)
+ *namep = NULL;
+
+ /* An absent source remains -ENOENT even without a staging address. */
+ ret = fw_fdt_get_source(&dev);
+ if (ret)
+ return ret;
+
+ filename = dev_read_string(dev, "filename");
+ if (!filename)
+ filename = FW_FDT_FILENAME;
+ if (namep) {
+ name = strdup(filename);
+ if (!name)
+ return -ENOMEM;
+ *namep = name;
+ }
+
+ if (!fdt_addr || !fdt_sizep)
+ return log_msg_ret("addr", -EINVAL);
+
+ ret = fw_fdt_load_source(dev, &fw);
+ if (ret)
+ return ret;
+
+ memcpy(map_sysmem(fdt_addr, fw.size), fw.fdt, fw.size);
+ *fdt_sizep = fw.size;
+ firmware_fdt_free(&fw);
+ log_debug("Using firmware-owned devicetree\n");
+
+ return 0;
+}
+
+static int fw_fdt_pre_remove(struct udevice *dev)
+{
+ struct firmware_fdt_priv *priv = dev_get_uclass_priv(dev);
+
+ fw_fdt_clear_cache(priv);
+
+ return 0;
+}
+
+UCLASS_DRIVER(firmware_fdt) = {
+ .id = UCLASS_FIRMWARE_FDT,
+ .name = "firmware-fdt",
+ .pre_remove = fw_fdt_pre_remove,
+ .per_device_auto = sizeof(struct firmware_fdt_priv),
+};
diff --git a/boot/firmware_fdt_block.c b/boot/firmware_fdt_block.c
new file mode 100644
index 00000000000..4569aaab91e
--- /dev/null
+++ b/boot/firmware_fdt_block.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#define LOG_CATEGORY UCLASS_FIRMWARE_FDT
+
+#include <blk.h>
+#include <dm.h>
+#include <firmware_fdt.h>
+#include <fs.h>
+#include <log.h>
+#include <part.h>
+#include <vsprintf.h>
+#include <dm/device-internal.h>
+#include <linux/string.h>
+
+struct firmware_fdt_block_priv {
+ struct blk_desc *desc;
+};
+
+static int fw_fdt_find_part(struct blk_desc *desc, const char *selector,
+ const char *type_uuid, const char *name)
+{
+ struct disk_partition info;
+ bool want_type = type_uuid && *type_uuid;
+ bool want_name = name && *name;
+ int part;
+
+ if (selector) {
+ char *end;
+ ulong pin;
+
+ pin = dectoul(selector, &end);
+ if (*end || !pin || pin > MAX_SEARCH_PARTITIONS)
+ return log_msg_ret("pin", -EINVAL);
+
+ if (part_get_info(desc, pin, &info))
+ return log_msg_ret("pin", -ENODEV);
+
+ return pin;
+ }
+
+ if (!want_type && !want_name)
+ return log_msg_ret("sel", -EINVAL);
+
+ for (part = 1; part <= MAX_SEARCH_PARTITIONS; part++) {
+ bool type_match, name_match;
+
+ if (part_get_info(desc, part, &info))
+ continue;
+
+ type_match = !want_type ||
+ !strncasecmp(disk_partition_type_guid(&info), type_uuid,
+ UUID_STR_LEN);
+ name_match = !want_name ||
+ !strcmp((const char *)info.name, name);
+
+ if (type_match && name_match)
+ return part;
+ }
+
+ return -ENODEV;
+}
+
+static int fw_fdt_block_read_fit(struct udevice *dev, const char *selector,
+ const char *filename, void **fitp,
+ ulong *sizep)
+{
+ struct firmware_fdt_block_priv *priv = dev_get_priv(dev);
+ const char *type_uuid, *part_name;
+ loff_t size;
+ int part, ret;
+
+ type_uuid = dev_read_string(dev, "partition-type-uuid");
+ part_name = dev_read_string(dev, "partition-name");
+ part = fw_fdt_find_part(priv->desc, selector, type_uuid, part_name);
+ if (part < 0)
+ return log_msg_ret("part", part);
+
+ ret = fs_set_blk_dev_with_part(priv->desc, part);
+ if (ret)
+ return log_msg_ret("fs", -EIO);
+
+ ret = fs_size(filename, &size);
+ if (ret)
+ return log_msg_ret("size", -EIO);
+
+ if (!size || size > CONFIG_FIRMWARE_FDT_FIT_MAX_SIZE)
+ return log_msg_ret("big", -E2BIG);
+
+ /* fs_size() consumed the mount. */
+ ret = fs_set_blk_dev_with_part(priv->desc, part);
+ if (ret)
+ return log_msg_ret("fs2", -EIO);
+
+ ret = fs_read_alloc(filename, size, 0, fitp);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ *sizep = size;
+
+ return 0;
+}
+
+static int fw_fdt_block_probe(struct udevice *dev)
+{
+ struct firmware_fdt_block_priv *priv = dev_get_priv(dev);
+ struct udevice *media, *blk;
+ ofnode store;
+ int ret;
+
+ store = ofnode_parse_phandle(dev_ofnode(dev), "firmware-fdt-store", 0);
+ if (!ofnode_valid(store))
+ return log_msg_ret("store", -EINVAL);
+
+ ret = device_get_global_by_ofnode(store, &media);
+ if (ret)
+ return log_msg_ret("media", ret == -ENOENT ? -ENODEV : ret);
+
+ ret = blk_get_from_parent(media, &blk);
+ if (ret)
+ return log_msg_ret("blk", ret == -ENOENT ? -ENODEV : ret);
+
+ priv->desc = dev_get_uclass_plat(blk);
+
+ return 0;
+}
+
+static const struct firmware_fdt_ops fw_fdt_block_ops = {
+ .read_fit = fw_fdt_block_read_fit,
+};
+
+static const struct udevice_id fw_fdt_block_ids[] = {
+ { .compatible = "u-boot,firmware-fdt-block" },
+ { }
+};
+
+U_BOOT_DRIVER(firmware_fdt_block) = {
+ .name = "firmware-fdt-block",
+ .id = UCLASS_FIRMWARE_FDT,
+ .of_match = fw_fdt_block_ids,
+ .ops = &fw_fdt_block_ops,
+ .probe = fw_fdt_block_probe,
+ .priv_auto = sizeof(struct firmware_fdt_block_priv),
+};
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
index 956a3d97c42..e65114ff835 100644
--- a/boot/image-fdt.c
+++ b/boot/image-fdt.c
@@ -468,7 +468,8 @@ static int select_fdt(struct bootm_headers *images, const char *select, u8 arch,
fdt_noffset = boot_get_fdt_fit(images, fdt_addr,
&fit_uname_fdt,
&fit_uname_config,
- arch, &load, &len);
+ arch, &load, &len,
+ NULL);
if (fdt_noffset < 0)
return -ENOENT;
diff --git a/boot/image-fit.c b/boot/image-fit.c
index ef90c5abd18..fa22a2e08b2 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -2513,7 +2513,7 @@ out:
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp)
+ int arch, ulong *datap, ulong *lenp, bool *ownedp)
{
int fdt_noffset, cfg_noffset, count;
const void *fit;
@@ -2533,6 +2533,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
#endif
fit_uname = fit_unamep ? *fit_unamep : NULL;
+ if (ownedp)
+ *ownedp = false;
if (fit_uname_configp && *fit_uname_configp) {
fit_uname_config_copy = strdup(*fit_uname_configp);
@@ -2691,8 +2693,11 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
out:
#ifdef CONFIG_OF_LIBFDT_OVERLAY
- if (fdt_noffset >= 0 && base_buf)
+ if (fdt_noffset >= 0 && base_buf) {
load = map_to_sysmem(base_buf);
+ if (ownedp)
+ *ownedp = true;
+ }
#endif
if (datap)
*datap = load;
diff --git a/doc/develop/uefi/firmware_fdt.rst b/doc/develop/uefi/firmware_fdt.rst
new file mode 100644
index 00000000000..6cf92b0c2d0
--- /dev/null
+++ b/doc/develop/uefi/firmware_fdt.rst
@@ -0,0 +1,118 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Firmware-owned devicetree
+=========================
+
+Some platforms following EBBR / Arm SystemReady DeviceTree treat the
+devicetree as part of the firmware: it lives in firmware-owned storage and is
+updated independently of the operating system, instead of being shipped in
+the OS image or on the EFI System Partition. U-Boot must read that
+devicetree, assemble it (base plus overlays) and hand it to the OS.
+
+The :c:func:`firmware_fdt_load` helper (``CONFIG_FIRMWARE_FDT``) provides a
+consumer-facing interface independently of standard boot. A firmware-FDT
+uclass separates the common FIT handling from storage drivers. The source
+compatible identifies its driver. The first implemented backend,
+``u-boot,firmware-fdt-block``, reads the FIT from a filesystem on a GPT
+partition of a block device.
+
+Additional driver backends may load the same FIT from other firmware storage,
+such as UBI on MTD. They reuse the common FIT configuration selection,
+verification and assembly, as well as the consumers below.
+
+The first consumers are the two EFI launch paths, so the firmware-owned
+devicetree is installed regardless of how the EFI application is started:
+
+ - the per-device EFI bootmeth (``bootmeth_efi``), and
+ - the EFI boot manager (``efi_bootmgr_run()``).
+
+In each case the assembled devicetree is installed into the EFI configuration
+table via :c:func:`efi_install_fdt`, exactly like any other source, so the OS
+cannot tell where it came from.
+
+This replaces vendor-specific firmware-devicetree commands while keeping
+storage discovery behind the source backend.
+
+The FIT
+-------
+
+The firmware source carries a FIT (by default ``fdt.itb``). Its images
+hold the base DTB and any overlays, and each of its configurations names one
+bootable combination through the standard ``fdt`` property::
+
+ configurations {
+ default = "conf-panel";
+ conf-panel {
+ fdt = "fdt-base", "fdt-panel";
+ };
+ };
+
+The helper selects a configuration, verifies it, loads the base devicetree
+and applies the listed overlays in order (via :c:func:`boot_get_fdt_fit`, the
+same code path ``bootm`` uses). The FIT describes and carries the devicetree
+as one artefact, updated atomically with it.
+
+Images in the FIT must be self-contained flat devicetrees: images that carry
+a ``load`` address and FITs using external data are rejected. With
+``FIT_SIGNATURE`` enabled, node and configuration names must not contain
+``@``.
+
+Configuration
+-------------
+
+The FIT source is described in the control devicetree (see
+``doc/device-tree-bindings/firmware-fdt.txt``). Each source compatible
+defines one storage backend and its locator properties. Exactly one source
+node may be enabled; multiple enabled source devices are rejected. The
+currently implemented ``u-boot,firmware-fdt-block`` backend points at the
+media device through the ``firmware-fdt-store`` phandle and identifies a GPT
+partition by type UUID and/or name, with an optional ``filename`` for the FIT
+path.
+
+A future backend may use different locator properties, for example an MTD
+device and UBI volume, while preserving the same FIT contents and the common
+selection, verification, assembly and fail-closed behavior.
+
+Two optional environment variables are runtime boot-policy overrides:
+``fw_fdt_part`` pins a partition number for A/B firmware selection and
+``fw_fdt_config`` names the FIT configuration for the current boot target.
+Without an explicit configuration, compatible best-match against the control
+devicetree is used; if there is no match, the FIT's ``default`` configuration
+is used. The ``fw_fdt_config`` value must name one configuration;
+configuration chaining with ``#`` is rejected so the selected base, overlay
+set and ordering remain one authenticated unit.
+
+The source device caches the FIT and assembled devicetree. Repeated bootflow
+candidates using the same selectors reuse the verified result. Changing
+``fw_fdt_part`` or ``fw_fdt_config`` invalidates the cache and rebuilds it.
+
+If no source is configured, the helper returns ``-ENOENT`` and the caller
+falls back to its normal devicetree source (ESP / built-in control FDT). If a
+source is configured but cannot be assembled, the error is fatal for that EFI
+launch path; this prevents a bad or unauthenticated firmware devicetree from
+being silently replaced by another devicetree source.
+
+A firmware-owned devicetree is the complete, authoritative devicetree: no
+other devicetree source is layered on top of it. In particular,
+extension-board overlays (``extension_scan()``) are intentionally not
+applied, since modifying the assembled (and, in secure mode, signed)
+devicetree would defeat the authenticated-combination model. Boards using
+extension boards should ship each supported combination as a FIT
+configuration and select it with ``fw_fdt_config``.
+
+Secure boot
+-----------
+
+Signing a FIT configuration authenticates the whole combination: the base,
+the overlay set and its ordering are the signed unit, and a tampered selector
+can only pick among combinations the firmware author pre-signed. Sign the
+FIT and inject the public key into U-Boot's control devicetree as
+usual::
+
+ mkimage -f fdt.its -k keys -K u-boot.dtb -r fdt.itb
+
+With ``CONFIG_FIT_SIGNATURE`` enabled and a required key in the control
+devicetree, verification is enforced by the standard verified-boot policy:
+an unsigned or tampered FIT is rejected and, because a configured source
+never falls back, the boot fails closed rather than booting an unverified
+devicetree.
diff --git a/doc/develop/uefi/index.rst b/doc/develop/uefi/index.rst
index e26b1fbe05c..67b100691bd 100644
--- a/doc/develop/uefi/index.rst
+++ b/doc/develop/uefi/index.rst
@@ -14,3 +14,4 @@ can be run an UEFI payload.
u-boot_on_efi.rst
iscsi.rst
fwu_updates.rst
+ firmware_fdt.rst
diff --git a/doc/device-tree-bindings/firmware-fdt.txt b/doc/device-tree-bindings/firmware-fdt.txt
new file mode 100644
index 00000000000..9e4c8937089
--- /dev/null
+++ b/doc/device-tree-bindings/firmware-fdt.txt
@@ -0,0 +1,160 @@
+U-Boot firmware-owned devicetree source (firmware-fdt)
+======================================================
+
+Some platforms (EBBR / Arm SystemReady DeviceTree) keep the devicetree in
+firmware-owned storage, updated independently of the operating system,
+rather than shipping it in the OS image or the EFI System Partition. The
+firmware storage carries a FIT: its images hold the base DTB and any
+overlays, and each of its configurations names one bootable combination
+through the standard 'fdt' property. U-Boot selects a configuration,
+verifies it per the usual verified-boot policy, assembles the devicetree
+(base plus overlays, in order) and installs it via the EFI configuration
+table.
+
+The source node's compatible selects the storage backend and its locator
+properties. This document defines the first backend,
+"u-boot,firmware-fdt-block", which reads the FIT from a filesystem on a GPT
+partition of a block device. Additional backends may load the same FIT from
+other firmware storage, for example a UBI volume on MTD, without changing
+FIT selection, verification, assembly or consumers. Each compatible is
+implemented as a firmware-FDT driver which supplies the FIT to that common
+code.
+
+For the block backend, a standalone node points at the media device that owns
+the partition by phandle. The node may live anywhere in the control devicetree;
+a node with status "disabled" is ignored. Exactly one firmware-FDT source may
+be enabled. Multiple enabled sources are ambiguous and rejected.
+
+
+firmware-fdt source node
+------------------------
+
+Required properties:
+
+compatible:
+ "u-boot,firmware-fdt-block" - the FIT lives in a filesystem on a
+ GPT partition of a block device. The suffix names the first implemented
+ backend. Sibling compatibles may define other source backends and their
+ storage-specific locator properties later.
+
+firmware-fdt-store:
+ phandle to the media device (UCLASS_MMC, ...) that owns the firmware
+ partition
+
+The partition is normally selected using the properties below. The
+'fw_fdt_part' environment variable is a runtime override which pins a
+partition number for A/B firmware selection. Without that override, the first
+partition matching every configured selector is used: when both
+'partition-type-uuid' and 'partition-name' are present, both must match (so
+a misprovisioned disk fails closed instead of silently selecting whichever
+same-type partition comes first).
+
+At least one of 'partition-type-uuid' or 'partition-name' must be present.
+
+Optional properties:
+
+partition-type-uuid:
+ GPT partition type UUID (string, case-insensitive) identifying the
+ firmware partition. When A/B firmware partitions share a type UUID,
+ 'partition-name' disambiguates between them.
+
+partition-name:
+ GPT partition name (string) identifying the firmware partition. Used to
+ disambiguate, or as a fallback when 'partition-type-uuid' is absent.
+
+filename:
+ Path of the FIT on the partition (default: "fdt.itb").
+
+
+Environment
+-----------
+
+Two optional environment variables override the source's normal selection for
+the current boot policy:
+
+ fw_fdt_part pin a specific partition number (A/B firmware partitions)
+ fw_fdt_config name of the FIT configuration to use for the current boot
+ target; when unset the best compatible match against the
+ control devicetree is used, falling back to the FIT's
+ default configuration
+
+``fw_fdt_config`` selects exactly one configuration; U-Boot's ``#`` syntax
+for composing several configurations is rejected so the base, overlay set and
+ordering remain one authenticated unit. Both values only choose among
+combinations the firmware author shipped; with signed configurations a
+tampered value cannot select an unsigned combination.
+
+The assembled result is cached by the source driver. Repeated consumers with
+the same two environment selectors reuse it; changing either selector causes
+the FIT to be read, verified and assembled again.
+
+
+The FIT
+-------
+
+This is a standard FIT image. Every image must be a flat devicetree
+('type = "flat_dt"') without a load address (images carrying a 'load'
+property are rejected), self-contained (no external data). For secure boot,
+sign the configurations and enable FIT_SIGNATURE with a required key in the
+control devicetree. Example source (.its):
+
+ /dts-v1/;
+ / {
+ description = "Firmware-owned devicetree";
+ #address-cells = <1>;
+
+ images {
+ fdt-base {
+ description = "base board devicetree";
+ data = /incbin/("board.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 { algo = "sha256"; };
+ };
+ fdt-panel {
+ description = "panel overlay";
+ data = /incbin/("panel.dtbo");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 { algo = "sha256"; };
+ };
+ };
+
+ configurations {
+ default = "conf-panel";
+ conf-panel {
+ fdt = "fdt-base", "fdt-panel";
+ signature-1 {
+ algo = "sha256,rsa2048";
+ key-name-hint = "fw";
+ sign-images = "fdt";
+ };
+ };
+ conf-base {
+ fdt = "fdt-base";
+ signature-1 {
+ algo = "sha256,rsa2048";
+ key-name-hint = "fw";
+ sign-images = "fdt";
+ };
+ };
+ };
+ };
+
+Note: with FIT_SIGNATURE enabled, node and configuration names must not
+contain the '@' character.
+
+
+Example
+-------
+
+ firmware-fdt {
+ compatible = "u-boot,firmware-fdt-block";
+ firmware-fdt-store = <&mmc0>;
+ partition-type-uuid =
+ "384e979b-eb76-435a-a3a6-1a071dbad91d";
+ partition-name = "firmware";
+ filename = "fdt.itb";
+ };
diff --git a/doc/usage/environment.rst b/doc/usage/environment.rst
index 80498853336..e5642b60449 100644
--- a/doc/usage/environment.rst
+++ b/doc/usage/environment.rst
@@ -243,6 +243,19 @@ fdtcontroladdr
device tree used by U-Boot when CONFIG_OF_CONTROL is
defined.
+fw_fdt_config
+ Runtime boot-policy override naming the configuration to select from a
+ firmware-owned devicetree FIT for the current boot target. If unset,
+ U-Boot uses compatible best-match against the control devicetree, falling
+ back to the FIT's default configuration. Configuration chaining with
+ ``#`` is not supported.
+
+fw_fdt_part
+ Runtime override for the partition containing the firmware-owned
+ devicetree FIT. This pins one side of an A/B firmware layout. If unset,
+ U-Boot uses the partition selectors in the
+ ``u-boot,firmware-fdt-block`` control-DT node.
+
initrd_high
restrict positioning of initrd images:
If this variable is not set, initrd images will be
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 36b5d87c304..1e63851ecd7 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -67,6 +67,7 @@ enum uclass_id {
UCLASS_FFA, /* Arm Firmware Framework for Armv8-A */
UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */
UCLASS_FIRMWARE, /* Firmware */
+ UCLASS_FIRMWARE_FDT, /* Firmware-owned devicetree source */
UCLASS_FPGA, /* FPGA device */
UCLASS_FUZZING_ENGINE, /* Fuzzing engine */
UCLASS_FS_FIRMWARE_LOADER, /* Generic loader */
diff --git a/include/firmware_fdt.h b/include/firmware_fdt.h
new file mode 100644
index 00000000000..fd25b0d7c5c
--- /dev/null
+++ b/include/firmware_fdt.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef __FIRMWARE_FDT_H
+#define __FIRMWARE_FDT_H
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+struct udevice;
+
+/**
+ * struct firmware_fdt_ops - operations implemented by a storage backend
+ *
+ * @read_fit: Read @filename selected by @part into an allocated buffer
+ */
+struct firmware_fdt_ops {
+ int (*read_fit)(struct udevice *dev, const char *part,
+ const char *filename, void **fitp, ulong *sizep);
+};
+
+/**
+ * struct firmware_fdt - an assembled, firmware-owned devicetree
+ *
+ * @fdt: pointer to the assembled devicetree in memory
+ * @size: size of the assembled devicetree, in bytes
+ * @name: FIT filename (for diagnostics)
+ * @fit: internal: buffer holding the FIT
+ * @fit_size: internal: size of the FIT, in bytes
+ * @fdt_owned: internal: true if @fdt is a separate allocation
+ * @borrowed: internal: true if the buffers belong to a source-device cache
+ *
+ * Release the returned view with firmware_fdt_free() after consuming it.
+ */
+struct firmware_fdt {
+ void *fdt;
+ ulong size;
+ const char *name;
+ void *fit;
+ ulong fit_size;
+ bool fdt_owned;
+ bool borrowed;
+};
+
+#if CONFIG_IS_ENABLED(FIRMWARE_FDT)
+/**
+ * firmware_fdt_load() - assemble the devicetree from firmware storage
+ *
+ * Assemble the devicetree (the base DTB with its overlays applied, as
+ * described by the FIT from the configured source) and return it in @out,
+ * ready to hand to the OS. The returned buffers are cached by the source
+ * device and remain valid until its selectors change or the device is removed.
+ *
+ * @out: returns the assembled devicetree on success
+ * Return: 0 on success; -ENOENT if no source is configured (the caller may
+ * fall back to its normal devicetree); another negative errno if a
+ * configured source fails to assemble (the caller must fail, never
+ * fall back)
+ */
+int firmware_fdt_load(struct firmware_fdt *out);
+
+/**
+ * firmware_fdt_free() - release an assembled-devicetree view
+ *
+ * Safe to call on a zeroed or already-freed @fw. Cached buffers remain owned
+ * by their source device.
+ *
+ * @fw: the assembled devicetree view to release
+ */
+void firmware_fdt_free(struct firmware_fdt *fw);
+
+/**
+ * firmware_fdt_stage() - stage a firmware-owned devicetree
+ *
+ * Check whether a source is configured, then assemble and copy its devicetree
+ * to @fdt_addr. Only -ENOENT permits a caller to try another source.
+ *
+ * Callers must read ``fdt_addr_r`` once and pass that value as @fdt_addr.
+ * The same value must be used for any fallback source, so an environment
+ * change cannot make the two paths disagree.
+ *
+ * @fdt_addr: destination address, normally the caller's ``fdt_addr_r`` value
+ * @fdt_sizep: returns the staged devicetree size
+ * @namep: if non-NULL and a source is found, returns an allocated copy of the
+ * FIT filename even if a later operation fails; the caller must free it
+ * Return: 0 if staged; -ENOENT if no source is configured; another negative
+ * errno if a configured source cannot be staged
+ */
+int firmware_fdt_stage(ulong fdt_addr, ulong *fdt_sizep, char **namep);
+#else
+static inline int firmware_fdt_load(struct firmware_fdt *out)
+{
+ return -ENOENT;
+}
+
+static inline void firmware_fdt_free(struct firmware_fdt *fw)
+{
+}
+
+static inline int firmware_fdt_stage(ulong fdt_addr, ulong *fdt_sizep,
+ char **namep)
+{
+ return -ENOENT;
+}
+#endif
+
+#endif /* __FIRMWARE_FDT_H */
diff --git a/include/image.h b/include/image.h
index 6edcb1995bf..0456e7c7a4c 100644
--- a/include/image.h
+++ b/include/image.h
@@ -719,12 +719,15 @@ int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
* @param arch Expected architecture (IH_ARCH_...)
* @param datap Returns address of loaded image
* @param lenp Returns length of loaded image
+ * @param ownedp Returns true if the loaded image is separately allocated
+ * and must be freed by the caller; may be NULL if the caller
+ * does not need this information
*
* Return: node offset of base image, or -ve error code on error
*/
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp);
+ int arch, ulong *datap, ulong *lenp, bool *ownedp);
/**
* fit_image_load() - load an image from a FIT
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 2/5] boot: add a firmware-owned devicetree source Carlo Caione
@ 2026-09-03 13:09 ` Carlo Caione
2026-09-03 13:09 ` [PATCH v3 4/5] efi_loader: bootmgr: install " Carlo Caione
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
4 siblings, 0 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
When a firmware-owned devicetree source is configured, stage it at the
same fdt_addr_r value used by the normal EFI bootmeth fallback and pass it
through bflow->fdt_addr to efi_install_fdt().
Use firmware_fdt_stage() in both disk and network paths. Only -ENOENT,
meaning that no source is configured, permits the existing ESP, network
or prior-stage fallback. Any failure after a source is configured ends
that bootflow.
The firmware-owned devicetree is complete and authoritative, so do not
apply extension-board overlays on top of it; those combinations belong in
the FIT.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
boot/bootmeth_efi.c | 45 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 39 insertions(+), 6 deletions(-)
diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
index e187dc39912..f58e90b6d21 100644
--- a/boot/bootmeth_efi.c
+++ b/boot/bootmeth_efi.c
@@ -17,6 +17,7 @@
#include <efi_loader.h>
#include <env.h>
#include <extension_board.h>
+#include <firmware_fdt.h>
#include <fs.h>
#include <malloc.h>
#include <mapmem.h>
@@ -100,7 +101,7 @@ static int distro_efi_check(struct udevice *dev, struct bootflow_iter *iter)
static int distro_efi_try_bootflow_files(struct udevice *dev,
struct bootflow *bflow)
{
- ulong fdt_addr, size, overlay_addr;
+ ulong fdt_addr, fw_fdt_size, size, overlay_addr;
const struct extension *extension;
struct fdt_header *working_fdt;
struct blk_desc *desc = NULL;
@@ -130,6 +131,22 @@ static int distro_efi_try_bootflow_files(struct udevice *dev,
fdt_addr = env_get_hex("fdt_addr_r", 0);
+ /*
+ * A staged firmware-owned devicetree is complete and authoritative,
+ * so return without considering any other devicetree source. The
+ * extension overlays below are deliberately not applied on top:
+ * such combinations belong in the FIT as configurations.
+ */
+ ret = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
+ &bflow->fdt_fname);
+ if (!ret) {
+ bflow->fdt_size = fw_fdt_size;
+ bflow->fdt_addr = fdt_addr;
+ return 0;
+ }
+ if (ret != -ENOENT)
+ return log_msg_ret("fwf", ret);
+
/* try the various available names */
ret = -ENOENT;
*fname = '\0';
@@ -222,9 +239,9 @@ static int distro_efi_read_bootflow_net(struct bootflow *bflow)
char file_addr[17], fname[256];
char *tftp_argv[] = {"tftp", file_addr, fname, NULL};
struct cmd_tbl cmdtp = {}; /* dummy */
- const char *addr_str, *fdt_addr_str, *bootfile_name;
+ const char *addr_str, *bootfile_name;
int ret, arch, size;
- ulong addr, fdt_addr;
+ ulong addr, fdt_addr, fw_fdt_size;
char str[36];
ret = get_efi_pxe_vci(str, sizeof(str));
@@ -268,6 +285,24 @@ static int distro_efi_read_bootflow_net(struct bootflow *bflow)
if (!bflow->fname)
return log_msg_ret("fi0", -ENOMEM);
+ /*
+ * Read fdt_addr_r once so the firmware-FDT source and network fallback
+ * below stage at the same address. A configured firmware-owned
+ * devicetree outranks the network-provided one (and the prior-stage /
+ * built-in devicetree below), so a DHCP/TFTP server cannot replace it.
+ */
+ fdt_addr = env_get_hex("fdt_addr_r", 0);
+ ret = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
+ &bflow->fdt_fname);
+ if (!ret) {
+ bflow->fdt_size = fw_fdt_size;
+ bflow->fdt_addr = fdt_addr;
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+ if (ret != -ENOENT)
+ return log_msg_ret("fwf", ret);
+
/* read the DT file also */
ret = efi_get_distro_fdt_name(fname, sizeof(fname), 0);
if (ret == -EALREADY) {
@@ -279,10 +314,8 @@ static int distro_efi_read_bootflow_net(struct bootflow *bflow)
return log_msg_ret("nam", ret);
}
- fdt_addr_str = env_get("fdt_addr_r");
- if (!fdt_addr_str)
+ if (!fdt_addr)
return log_msg_ret("fdt", -EINVAL);
- fdt_addr = hextoul(fdt_addr_str, NULL);
sprintf(file_addr, "%lx", fdt_addr);
bflow->fdt_fname = strdup(fname);
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 4/5] efi_loader: bootmgr: install the firmware-owned devicetree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (2 preceding siblings ...)
2026-09-03 13:09 ` [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree Carlo Caione
@ 2026-09-03 13:09 ` Carlo Caione
2026-09-08 8:37 ` Ilias Apalodimas
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
4 siblings, 1 reply; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
SystemReady DeviceTree can boot through the UEFI boot manager without
going through the per-device EFI bootmeth. Use firmware_fdt_stage() here
as well so both EFI launch paths install the same firmware-owned
devicetree.
Keep the firmware-FDT size in an ulong separate from the EFI allocation
size and retain the FIT filename for diagnostics. The precedence is an
explicitly passed FDT, firmware-owned FDT, Boot#### load-option FDT, then
distro/ESP FDT. A configured source fails closed, and assembly failures
are reported as EFI_DEVICE_ERROR.
Route load-option errors through the common cleanup path while adding it.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
lib/efi_loader/efi_bootmgr.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
index 6b5dad21b06..4049ebbc0dd 100644
--- a/lib/efi_loader/efi_bootmgr.c
+++ b/lib/efi_loader/efi_bootmgr.c
@@ -14,8 +14,10 @@
#include <efi.h>
#include <efi_device_path.h>
#include <env.h>
+#include <firmware_fdt.h>
#include <log.h>
#include <malloc.h>
+#include <mapmem.h>
#include <net.h>
#include <part.h>
#include <efi_loader.h>
@@ -1296,7 +1298,8 @@ out:
*
* Invoke the EFI boot manager and execute a binary according to its boot
* options. The devicetree precedence, from highest to lowest, is an FDT
- * passed in @fdt, the Boot#### load-option FDT, then the distro/ESP FDT.
+ * passed in @fdt, a configured firmware-owned FDT, the Boot#### load-option
+ * FDT, then the distro/ESP FDT.
*
* Return: status code
*/
@@ -1306,7 +1309,9 @@ efi_status_t efi_bootmgr_run(void *fdt)
void *load_options;
efi_status_t ret;
void *fdt_lo = NULL, *fdt_distro = NULL;
+ char *fw_fdt_fname = NULL;
efi_uintn_t fdt_size;
+ ulong fw_fdt_size;
/* Initialize EFI drivers */
ret = efi_init_obj_list();
@@ -1320,13 +1325,30 @@ efi_status_t efi_bootmgr_run(void *fdt)
}
if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
+ if (!fdt) {
+ ulong fdt_addr = env_get_hex("fdt_addr_r", 0);
+ int err;
+
+ err = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
+ &fw_fdt_fname);
+ if (!err) {
+ fdt = map_sysmem(fdt_addr, fw_fdt_size);
+ } else if (err != -ENOENT) {
+ log_err("Failed to load firmware devicetree '%s' (err %d)\n",
+ fw_fdt_fname ? fw_fdt_fname : "fdt.itb", err);
+ ret = EFI_DEVICE_ERROR;
+ goto out;
+ }
+ }
+
if (!fdt) {
ret = load_fdt_from_load_option(&fdt_lo);
if (ret != EFI_SUCCESS)
- return ret;
+ goto out;
if (fdt_lo)
fdt = fdt_lo;
}
+
if (!fdt) {
efi_load_distro_fdt(handle, &fdt_distro, &fdt_size);
fdt = fdt_distro;
@@ -1339,11 +1361,14 @@ efi_status_t efi_bootmgr_run(void *fdt)
*/
ret = efi_install_fdt(fdt);
+out:
if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
+ /* efi_install_fdt() has copied the devicetree */
free(fdt_lo);
if (fdt_distro)
efi_free_pages((uintptr_t)fdt_distro,
efi_size_in_pages(fdt_size));
+ free(fw_fdt_fname);
}
if (ret != EFI_SUCCESS) {
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 5/5] test: boot: add firmware-FDT source tests
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (3 preceding siblings ...)
2026-09-03 13:09 ` [PATCH v3 4/5] efi_loader: bootmgr: install " Carlo Caione
@ 2026-09-03 13:09 ` Carlo Caione
4 siblings, 0 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-03 13:09 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, Tom Rini, Carlo Caione,
Simon Glass, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
Add sandbox coverage for firmware_fdt_load(). The Python fixture creates
mmc11.img in persistent_data_dir with A/B firmware partitions. Its first
FAT partition carries five FITs covering base-plus-overlay assembly,
compatible best-match, external-data rejection and corrupt base or
overlay hashes.
Construct the complete MMC provider and firmware-FDT source topology in
each flat-tree test, including the provider phandle and source driver.
Nothing is added to the shared sandbox control devicetree, and the
sandbox test framework restores its FDT snapshot even when an assertion
fails.
Cover default and explicit configuration selection, compatible
best-match, cached-result reuse and selector invalidation, assembled-FDT
ownership, staging and its returned filename. Cover fail-closed handling
for corrupt images, configuration chaining, a missing FIT, configuration
or partition, multiple source devices, an invalid source phandle,
external data and mismatched partition selectors. Verify that only a
genuinely absent source returns -ENOENT.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
configs/sandbox_defconfig | 1 +
test/boot/Makefile | 1 +
test/boot/firmware_fdt.c | 498 ++++++++++++++++++++++++++++++++++++++++++++++
test/py/tests/test_ut.py | 183 +++++++++++++++++
4 files changed, 683 insertions(+)
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 79f46317e45..b2affd96831 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -27,6 +27,7 @@ CONFIG_FIT_VERITY=y
CONFIG_FIT_VERBOSE=y
CONFIG_BOOTMETH_ANDROID=y
CONFIG_BOOTMETH_RAUC=y
+CONFIG_FIRMWARE_FDT=y
CONFIG_UPL=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_MEASURED_BOOT=y
diff --git a/test/boot/Makefile b/test/boot/Makefile
index 59a87028704..681c8c4c94f 100644
--- a/test/boot/Makefile
+++ b/test/boot/Makefile
@@ -4,6 +4,7 @@
ifdef CONFIG_UT_BOOTSTD
obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o
+obj-$(CONFIG_FIRMWARE_FDT) += firmware_fdt.o
obj-$(CONFIG_FIT) += image.o
ifdef CONFIG_VIDEO_SANDBOX_SDL
diff --git a/test/boot/firmware_fdt.c b/test/boot/firmware_fdt.c
new file mode 100644
index 00000000000..8c4ac3dca01
--- /dev/null
+++ b/test/boot/firmware_fdt.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for the firmware-owned devicetree source (firmware_fdt_load()).
+ *
+ * Uses a sandbox mmc image (mmc11) carrying a GPT 'firmware' partition with
+ * a FAT filesystem holding five FITs: fdt.itb, fdt-best.itb, fdt-ext.itb,
+ * fdt-corrupt.itb and fdt-corrupt-overlay.itb. The image is built by
+ * setup_firmware_fdt_image() in test/py/tests/test_ut.py.
+ *
+ * The tests create the complete source topology at runtime, including the
+ * mmc11 provider and its phandle. The shared test.dts stays unconfigured,
+ * while the sandbox DM test framework restores its FDT snapshot after every
+ * test, including a failed one.
+ */
+
+#include <dm.h>
+#include <env.h>
+#include <firmware_fdt.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <os.h>
+#include <asm/global_data.h>
+#include <dm/lists.h>
+#include <dm/ofnode.h>
+#include <dm/root.h>
+#include <linux/libfdt.h>
+#include <test/test.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define FWFDT_NODE_PATH "/fw-fdt"
+#define FWFDT_STORE_PROP "firmware-fdt-store"
+#define FWFDT_STORE_PHANDLE 0x10000
+#define FWFDT_TYPE_UUID "384e979b-eb76-435a-a3a6-1a071dbad91d"
+#define FWFDT_TEST_FLAGS (UTF_DM | UTF_SCAN_FDT | UTF_FLAT_TREE)
+
+static ofnode fwfdt_source_node(void)
+{
+ return ofnode_path(FWFDT_NODE_PATH);
+}
+
+/* Bind the runtime-created media and, when present, source nodes */
+static int fwfdt_bind_nodes(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ ofnode node;
+
+ node = ofnode_path("/mmc11");
+ ut_assert(ofnode_valid(node));
+ ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false));
+
+ node = fwfdt_source_node();
+ if (ofnode_valid(node))
+ ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false));
+
+ return 0;
+}
+
+/*
+ * Create the full firmware-FDT topology. Set @with_source to false for the
+ * no-source test, which still needs the media device.
+ */
+static int fwfdt_configure(struct unit_test_state *uts, bool with_source)
+{
+ char fname[256];
+ ofnode root, mmc, src;
+
+ ut_assertok(os_persistent_file(fname, sizeof(fname), "mmc11.img"));
+ root = oftree_root(oftree_default());
+ ut_assertok(ofnode_add_subnode(root, "mmc11", &mmc));
+ ut_assertok(ofnode_write_string(mmc, "compatible", "sandbox,mmc"));
+ ut_assertok(ofnode_write_string(mmc, "filename", fname));
+ ut_assertok(ofnode_write_u32(mmc, "phandle", FWFDT_STORE_PHANDLE));
+
+ if (!with_source)
+ return 0;
+
+ ut_assertok(ofnode_add_subnode(root, "fw-fdt", &src));
+ ut_assertok(ofnode_write_string(src, "compatible",
+ "u-boot,firmware-fdt-block"));
+ ut_assertok(ofnode_write_u32(src, FWFDT_STORE_PROP,
+ FWFDT_STORE_PHANDLE));
+ ut_assertok(ofnode_write_string(src, "partition-type-uuid",
+ FWFDT_TYPE_UUID));
+ ut_assertok(ofnode_write_string(src, "partition-name", "firmware"));
+ ut_assertok(ofnode_write_string(src, "filename", "fdt.itb"));
+
+ return 0;
+}
+
+/* Clear the environment values used by the tests */
+static int fwfdt_clear_env(struct unit_test_state *uts)
+{
+ env_set("fw_fdt_part", NULL);
+ env_set("fw_fdt_config", NULL);
+
+ return 0;
+}
+
+/* Happy path: the FIT's default configuration applies base + overlay */
+static int firmware_fdt_test_load(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ void *fdt;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(firmware_fdt_load(&fw));
+
+ ut_asserteq_str("fdt.itb", fw.name);
+ ut_assert(fw.size > 0);
+ ut_assert(fw.fdt_owned);
+
+ fdt = fw.fdt;
+ ut_assertok(fdt_check_header(fdt));
+ /* the size reports the packed devicetree, not a padded buffer */
+ ut_asserteq(fw.size, fdt_totalsize(fdt));
+ /* the base property is present... */
+ ut_assertnonnull(fdt_getprop(fdt, 0, "fw-base-prop", NULL));
+ /* ...and the overlay was applied on top */
+ ut_assertnonnull(fdt_getprop(fdt, 0, "fw-overlay-prop", NULL));
+
+ firmware_fdt_free(&fw);
+ ut_assertnull(fw.fdt);
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_load, FWFDT_TEST_FLAGS);
+
+/* 'fw_fdt_config' selects another configuration the FIT ships */
+static int firmware_fdt_test_select(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ void *fdt;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(env_set("fw_fdt_config", "conf-base"));
+ ut_assertok(firmware_fdt_load(&fw));
+
+ fdt = fw.fdt;
+ ut_assert(!fw.fdt_owned);
+ ut_assertnonnull(fdt_getprop(fdt, 0, "fw-base-prop", NULL));
+ /* the base-only configuration applies no overlay */
+ ut_assertnull(fdt_getprop(fdt, 0, "fw-overlay-prop", NULL));
+
+ firmware_fdt_free(&fw);
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_select, FWFDT_TEST_FLAGS);
+
+/* Repeated loads reuse the cache until a runtime selector changes */
+static int firmware_fdt_test_cache(struct unit_test_state *uts)
+{
+ struct firmware_fdt first, second, selected;
+ void *cached_fdt;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(firmware_fdt_load(&first));
+ cached_fdt = first.fdt;
+ firmware_fdt_free(&first);
+
+ ut_assertok(firmware_fdt_load(&second));
+ ut_asserteq_ptr(cached_fdt, second.fdt);
+ firmware_fdt_free(&second);
+
+ ut_assertok(env_set("fw_fdt_part", "9"));
+ ut_asserteq(-ENODEV, firmware_fdt_load(&selected));
+ ut_assertok(env_set("fw_fdt_part", NULL));
+
+ ut_assertok(env_set("fw_fdt_config", "conf-base"));
+ ut_assertok(firmware_fdt_load(&selected));
+ ut_assertnonnull(fdt_getprop(selected.fdt, 0, "fw-base-prop", NULL));
+ ut_assertnull(fdt_getprop(selected.fdt, 0, "fw-overlay-prop", NULL));
+ firmware_fdt_free(&selected);
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_cache, FWFDT_TEST_FLAGS);
+
+/*
+ * Configuration chaining could assemble a combination which was never signed
+ * as one unit. Only one FIT configuration may be selected.
+ */
+static int firmware_fdt_test_config_chain(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(env_set("fw_fdt_config", "conf-base#conf-overlay"));
+ ut_asserteq(-EINVAL, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_config_chain, FWFDT_TEST_FLAGS);
+
+/* The shared EFI helper stages the result and owns its returned filename */
+static int firmware_fdt_test_stage(struct unit_test_state *uts)
+{
+ char *name;
+ ulong size;
+ void *buf;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ buf = malloc(CONFIG_FIRMWARE_FDT_MAX_SIZE);
+ ut_assertnonnull(buf);
+ ut_assertok(firmware_fdt_stage(map_to_sysmem(buf), &size, &name));
+ ut_asserteq_str("fdt.itb", name);
+ ut_asserteq(size, fdt_totalsize(buf));
+ ut_assertnonnull(fdt_getprop(buf, 0, "fw-base-prop", NULL));
+ ut_assertnonnull(fdt_getprop(buf, 0, "fw-overlay-prop", NULL));
+ free(name);
+ free(buf);
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_stage, FWFDT_TEST_FLAGS);
+
+/* Compatible best-match against the control DT selects conf-sandbox */
+static int firmware_fdt_test_best_match(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ const char *value;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ ut_assertok(ofnode_write_string(node, "filename", "fdt-best.itb"));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(firmware_fdt_load(&fw));
+ value = fdt_getprop(fw.fdt, 0, "fw-best-prop", NULL);
+ ut_assertnonnull(value);
+ ut_asserteq_str("sandbox", value);
+ firmware_fdt_free(&fw);
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_best_match, FWFDT_TEST_FLAGS);
+
+/* A corrupted base fails hash verification and cannot fall back */
+static int firmware_fdt_test_corrupt(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ ut_assertok(ofnode_write_string(node, "filename", "fdt-corrupt.itb"));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-EACCES, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_corrupt, FWFDT_TEST_FLAGS);
+
+/* A corrupted overlay is fatal too; it must never be silently skipped */
+static int firmware_fdt_test_corrupt_overlay(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ ut_assertok(ofnode_write_string(node, "filename",
+ "fdt-corrupt-overlay.itb"));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-EACCES, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_corrupt_overlay, FWFDT_TEST_FLAGS);
+
+/*
+ * A selector naming a configuration the FIT does not ship must be
+ * fatal: -ENOENT strictly means "no source configured", so the inner miss
+ * must not leak out and let the caller fall back (fail closed).
+ */
+static int firmware_fdt_test_bad_config(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(env_set("fw_fdt_config", "conf-nonexistent"));
+ ut_asserteq(-ENODEV, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_bad_config, FWFDT_TEST_FLAGS);
+
+/* Pinning a partition that does not exist is a hard error (fail closed) */
+static int firmware_fdt_test_no_part(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_assertok(env_set("fw_fdt_part", "9"));
+ ut_asserteq(-ENODEV, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_no_part, FWFDT_TEST_FLAGS);
+
+/* A configured source with a missing FIT is fatal, not an absent source */
+static int firmware_fdt_test_no_fit(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ ut_assertok(ofnode_write_string(node, "filename", "missing.itb"));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-EIO, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_no_fit, FWFDT_TEST_FLAGS);
+
+/*
+ * Without a source node, -ENOENT is the only result which lets callers fall
+ * back. This also proves the provider alone does not configure the feature.
+ */
+static int firmware_fdt_test_no_source(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+
+ ut_assertok(fwfdt_configure(uts, false));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-ENOENT, firmware_fdt_load(&fw));
+ /* Source detection precedes address validation in the staging helper */
+ ut_asserteq(-ENOENT, firmware_fdt_stage(0, NULL, NULL));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_no_source, FWFDT_TEST_FLAGS);
+
+/* The configuration is ambiguous if more than one source is enabled */
+static int firmware_fdt_test_multiple_sources(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ struct udevice *dev;
+ ofnode node, root;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ root = oftree_root(oftree_default());
+ ut_assertok(ofnode_add_subnode(root, "fw-fdt-second", &node));
+ ut_assertok(ofnode_write_string(node, "compatible",
+ "u-boot,firmware-fdt-block"));
+ ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false));
+
+ ut_asserteq(-EINVAL, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_multiple_sources, FWFDT_TEST_FLAGS);
+
+/*
+ * A 'firmware-fdt-store' phandle that does not resolve is a broken
+ * configuration and must be fatal, not mistaken for "no source".
+ */
+static int firmware_fdt_test_bad_source(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ fdt32_t bad;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+
+ bad = cpu_to_fdt32(0x7fffffff);
+ ut_assertok(ofnode_write_prop(node, FWFDT_STORE_PROP, &bad,
+ sizeof(bad), true));
+ ut_assertok(fwfdt_bind_nodes(uts));
+ ut_asserteq(-EINVAL, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_bad_source, FWFDT_TEST_FLAGS);
+
+/* A FIT using external data is refused: it must be self-contained */
+static int firmware_fdt_test_external(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ ut_assertok(ofnode_write_string(node, "filename", "fdt-ext.itb"));
+
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-EINVAL, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_external, FWFDT_TEST_FLAGS);
+
+/*
+ * When both partition-type-uuid and partition-name are configured, both
+ * must match: a name matching nothing must not fall back to whichever
+ * same-type (A/B) partition comes first.
+ */
+static int firmware_fdt_test_part_mismatch(struct unit_test_state *uts)
+{
+ struct firmware_fdt fw;
+ ofnode node;
+
+ ut_assertok(fwfdt_configure(uts, true));
+
+ node = fwfdt_source_node();
+ ut_assert(ofnode_valid(node));
+ /* the type UUID matches both A/B partitions; this name matches none */
+ ut_assertok(ofnode_write_string(node, "partition-name", "nomatch"));
+
+ ut_assertok(fwfdt_bind_nodes(uts));
+
+ ut_asserteq(-ENODEV, firmware_fdt_load(&fw));
+
+ ut_assertok(fwfdt_clear_env(uts));
+
+ return 0;
+}
+
+BOOTSTD_TEST(firmware_fdt_test_part_mismatch, FWFDT_TEST_FLAGS);
diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index fa50c8008a5..f2ecc547ac7 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -614,6 +614,188 @@ def setup_rauc_image(ubman):
boot.cleanup()
root.cleanup()
+def setup_firmware_fdt_image(ubman):
+ """Create mmc11.img for the firmware_fdt tests
+
+ A GPT disk with two firmware partitions (A/B) sharing a firmware type
+ UUID; partition 1 (label 'firmware') holds a FAT filesystem with the
+ FITs fdt.itb, fdt-best.itb, fdt-ext.itb, fdt-corrupt.itb and
+ fdt-corrupt-overlay.itb. The main FIT carries a base DTB and an overlay,
+ with two configurations: the default applies the overlay, 'conf-base'
+ does not.
+ """
+ Partition = collections.namedtuple('part', 'start,size,name')
+ parts = {}
+
+ mmc_dev = 11
+ fname = os.path.join(ubman.config.persistent_data_dir,
+ f'mmc{mmc_dev}.img')
+ fw_type = '384e979b-eb76-435a-a3a6-1a071dbad91d'
+ sect_size = 512
+
+ # Compile a tiny base DTB and an overlay, then wrap them in the FIT
+ src = os.path.join(ubman.config.persistent_data_dir, 'fwfdt')
+ mkdir_cond(src)
+ base_dtb = os.path.join(src, 'base.dtb')
+ match_dtb = os.path.join(src, 'match.dtb')
+ ovl_dtbo = os.path.join(src, 'overlay.dtbo')
+ utils.run_and_log(
+ ubman, f'dtc -O dtb -o {base_dtb}',
+ stdin=b'/dts-v1/; / { compatible = "test,fw-fdt-base"; '
+ b'fw-base-prop = "base"; };')
+ utils.run_and_log(
+ ubman, f'dtc -O dtb -o {ovl_dtbo}',
+ stdin=b'/dts-v1/; /plugin/; &{/} { fw-overlay-prop = "applied"; };')
+ utils.run_and_log(
+ ubman, f'dtc -O dtb -o {match_dtb}',
+ stdin=b'/dts-v1/; / { compatible = "sandbox"; '
+ b'fw-best-prop = "sandbox"; };')
+
+ its = os.path.join(src, 'fdt.its')
+ with open(its, 'w', encoding='ascii') as outf:
+ outf.write(f'''
+/dts-v1/;
+/ {{
+\tdescription = "Firmware-owned OS devicetree";
+\t#address-cells = <1>;
+
+\timages {{
+\t\tfdt-base {{
+\t\t\tdata = /incbin/("{base_dtb}");
+\t\t\ttype = "flat_dt";
+\t\t\tarch = "sandbox";
+\t\t\tcompression = "none";
+\t\t\thash-1 {{ algo = "sha256"; }};
+\t\t}};
+\t\tfdt-overlay {{
+\t\t\tdata = /incbin/("{ovl_dtbo}");
+\t\t\ttype = "flat_dt";
+\t\t\tarch = "sandbox";
+\t\t\tcompression = "none";
+\t\t\thash-1 {{ algo = "sha256"; }};
+\t\t}};
+\t}};
+
+\tconfigurations {{
+\t\tdefault = "conf-overlay";
+\t\tconf-overlay {{
+\t\t\tfdt = "fdt-base", "fdt-overlay";
+\t\t}};
+\t\tconf-base {{
+\t\t\tfdt = "fdt-base";
+\t\t}};
+\t}};
+}};
+''')
+
+ fs_dir = os.path.join(src, 'fs')
+ mkdir_cond(fs_dir)
+ mkimage = os.path.join(ubman.config.build_dir, 'tools/mkimage')
+ fit = os.path.join(fs_dir, 'fdt.itb')
+ utils.run_and_log(ubman, f'{mkimage} -f {its} {fit}')
+
+ # An external-data variant, which firmware_fdt_load() must refuse
+ utils.run_and_log(
+ ubman, f'{mkimage} -E -f {its} {os.path.join(fs_dir, "fdt-ext.itb")}')
+
+ # A FIT with two distinct compatibles for CONFIG_FIT_BEST_MATCH coverage
+ best_its = os.path.join(src, 'fdt-best.its')
+ with open(best_its, 'w', encoding='ascii') as outf:
+ outf.write(f'''
+/dts-v1/;
+/ {{
+\tdescription = "Firmware-owned compatible selection test";
+\t#address-cells = <1>;
+
+\timages {{
+\t\tfdt-generic {{
+\t\t\tdata = /incbin/("{base_dtb}");
+\t\t\ttype = "flat_dt";
+\t\t\tarch = "sandbox";
+\t\t\tcompression = "none";
+\t\t\thash-1 {{ algo = "sha256"; }};
+\t\t}};
+\t\tfdt-sandbox {{
+\t\t\tdata = /incbin/("{match_dtb}");
+\t\t\ttype = "flat_dt";
+\t\t\tarch = "sandbox";
+\t\t\tcompression = "none";
+\t\t\thash-1 {{ algo = "sha256"; }};
+\t\t}};
+\t}};
+
+\tconfigurations {{
+\t\tdefault = "conf-generic";
+\t\tconf-generic {{
+\t\t\tfdt = "fdt-generic";
+\t\t}};
+\t\tconf-sandbox {{
+\t\t\tfdt = "fdt-sandbox";
+\t\t}};
+\t}};
+}};
+''')
+ utils.run_and_log(
+ ubman,
+ f'{mkimage} -f {best_its} {os.path.join(fs_dir, "fdt-best.itb")}')
+
+ # Corrupt base data without updating its hash: loading must fail closed
+ with open(fit, 'rb') as inf:
+ corrupt_data = bytearray(inf.read())
+ with open(base_dtb, 'rb') as inf:
+ base_data = inf.read()
+ data_offset = corrupt_data.find(base_data)
+ if data_offset < 0:
+ raise ValueError('Cannot locate base DTB in firmware-FDT FIT')
+ corrupt_data[data_offset + len(base_data) - 1] ^= 1
+ with open(os.path.join(fs_dir, 'fdt-corrupt.itb'), 'wb') as outf:
+ outf.write(corrupt_data)
+
+ # A corrupt overlay must be fatal too, never silently skipped
+ with open(fit, 'rb') as inf:
+ corrupt_data = bytearray(inf.read())
+ with open(ovl_dtbo, 'rb') as inf:
+ overlay_data = inf.read()
+ data_offset = corrupt_data.find(overlay_data)
+ if data_offset < 0:
+ raise ValueError('Cannot locate overlay DTBO in firmware-FDT FIT')
+ corrupt_data[data_offset + len(overlay_data) - 1] ^= 1
+ with open(os.path.join(fs_dir, 'fdt-corrupt-overlay.itb'), 'wb') as outf:
+ outf.write(corrupt_data)
+
+ fat_img = fs_helper.mk_fs(ubman.config, 'vfat', 1 << 20, 'fwfdt',
+ src_dir=fs_dir)
+ with open(fat_img, 'rb') as inf:
+ fat_data = inf.read()
+
+ # GPT with two same-type firmware partitions; the FAT goes in partition 1
+ fat_sects = (len(fat_data) + sect_size - 1) // sect_size
+ utils.run_and_log(ubman, f'qemu-img create {fname} 8M')
+ utils.run_and_log(ubman, f'cgpt create {fname}')
+ ptr = 40
+ for num, label in ((1, 'firmware'), (2, 'firmware_b')):
+ utils.run_and_log(
+ ubman,
+ f'cgpt add -i {num} -b {ptr} -s {fat_sects} -t {fw_type} '
+ f'-l {label} {fname}')
+ ptr += fat_sects
+ utils.run_and_log(ubman, f'cgpt boot -p {fname}')
+ out = utils.run_and_log(ubman, f'cgpt show -q {fname}')
+ for line in out.splitlines():
+ start, size, num, name = line.split(maxsplit=3)
+ parts[int(num)] = Partition(int(start), int(size), name)
+
+ # Splice the FAT image into partition 1
+ with open(fname, 'rb') as inf:
+ disk_data = inf.read()
+ start = parts[1].start * sect_size
+ disk_data = disk_data[:start] + fat_data + disk_data[start + len(fat_data):]
+ with open(fname, 'wb') as outf:
+ outf.write(disk_data)
+
+ return fname
+
+
@pytest.mark.buildconfigspec('cmd_bootflow')
@pytest.mark.buildconfigspec('sandbox')
def test_ut_dm_init_bootstd(ubman):
@@ -626,6 +808,7 @@ def test_ut_dm_init_bootstd(ubman):
setup_android_image(ubman)
setup_efi_image(ubman)
setup_rauc_image(ubman)
+ setup_firmware_fdt_image(ubman)
# Restart so that the new mmc1.img is picked up
ubman.restart_uboot()
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/5] efi_loader: bootmgr: install the firmware-owned devicetree
2026-09-03 13:09 ` [PATCH v3 4/5] efi_loader: bootmgr: install " Carlo Caione
@ 2026-09-08 8:37 ` Ilias Apalodimas
2026-09-08 9:09 ` Carlo Caione
0 siblings, 1 reply; 10+ messages in thread
From: Ilias Apalodimas @ 2026-09-08 8:37 UTC (permalink / raw)
To: Carlo Caione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Tom Rini, Simon Glass,
Quentin Schulz, Marek Vasut, Johan Jonker, Jonas Karlman,
Randolph Sapp, Daniel Golle, James Hilliard, Aristo Chen,
Peng Fan, Vincent Jardin, Peter Robinson
[-- Attachment #1: Type: text/plain, Size: 4286 bytes --]
Hi Carlo,
On Thu, 3 Sept 2026 at 16:10, Carlo Caione <ccaione@baylibre.com> wrote:
> SystemReady DeviceTree can boot through the UEFI boot manager without
> going through the per-device EFI bootmeth. Use firmware_fdt_stage() here
> as well so both EFI launch paths install the same firmware-owned
> devicetree.
>
> Keep the firmware-FDT size in an ulong separate from the EFI allocation
> size and retain the FIT filename for diagnostics. The precedence is an
> explicitly passed FDT, firmware-owned FDT, Boot#### load-option FDT, then
> distro/ESP FDT. A configured source fails closed, and assembly failures
> are reported as EFI_DEVICE_ERROR.
>
> Route load-option errors through the common cleanup path while adding it.
>
> Signed-off-by: Carlo Caione <ccaione@baylibre.com>
> ---
> lib/efi_loader/efi_bootmgr.c | 29 +++++++++++++++++++++++++++--
> 1 file changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> index 6b5dad21b06..4049ebbc0dd 100644
> --- a/lib/efi_loader/efi_bootmgr.c
> +++ b/lib/efi_loader/efi_bootmgr.c
> @@ -14,8 +14,10 @@
> #include <efi.h>
> #include <efi_device_path.h>
> #include <env.h>
> +#include <firmware_fdt.h>
> #include <log.h>
> #include <malloc.h>
> +#include <mapmem.h>
> #include <net.h>
> #include <part.h>
> #include <efi_loader.h>
> @@ -1296,7 +1298,8 @@ out:
> *
> * Invoke the EFI boot manager and execute a binary according to its boot
> * options. The devicetree precedence, from highest to lowest, is an FDT
> - * passed in @fdt, the Boot#### load-option FDT, then the distro/ESP FDT.
> + * passed in @fdt, a configured firmware-owned FDT, the Boot####
> load-option
> + * FDT, then the distro/ESP FDT.
> *
> * Return: status code
> */
> @@ -1306,7 +1309,9 @@ efi_status_t efi_bootmgr_run(void *fdt)
> void *load_options;
> efi_status_t ret;
> void *fdt_lo = NULL, *fdt_distro = NULL;
> + char *fw_fdt_fname = NULL;
> efi_uintn_t fdt_size;
> + ulong fw_fdt_size;
>
> /* Initialize EFI drivers */
> ret = efi_init_obj_list();
> @@ -1320,13 +1325,30 @@ efi_status_t efi_bootmgr_run(void *fdt)
> }
>
> if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
> + if (!fdt) {
> + ulong fdt_addr = env_get_hex("fdt_addr_r", 0);
> + int err;
>
+
>
Any reason we are doing the _hex variant instead of env_get? Don't we want
to bail out if the env variable isnt defined instead of picking 0x0 ?
+ err = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
> + &fw_fdt_fname);
> + if (!err) {
> + fdt = map_sysmem(fdt_addr, fw_fdt_size);
> + } else if (err != -ENOENT) {
> + log_err("Failed to load firmware
> devicetree '%s' (err %d)\n",
> + fw_fdt_fname ? fw_fdt_fname :
> "fdt.itb", err);
> + ret = EFI_DEVICE_ERROR;
> + goto out;
> + }
> + }
> +
> if (!fdt) {
> ret = load_fdt_from_load_option(&fdt_lo);
> if (ret != EFI_SUCCESS)
> - return ret;
> + goto out;
> if (fdt_lo)
> fdt = fdt_lo;
> }
> +
> if (!fdt) {
> efi_load_distro_fdt(handle, &fdt_distro,
> &fdt_size);
> fdt = fdt_distro;
> @@ -1339,11 +1361,14 @@ efi_status_t efi_bootmgr_run(void *fdt)
> */
> ret = efi_install_fdt(fdt);
>
> +out:
> if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
> + /* efi_install_fdt() has copied the devicetree */
> free(fdt_lo);
> if (fdt_distro)
> efi_free_pages((uintptr_t)fdt_distro,
> efi_size_in_pages(fdt_size));
> + free(fw_fdt_fname);
> }
>
> if (ret != EFI_SUCCESS) {
>
> --
> 2.55.0
>
>
Cheers
/Ilias
[-- Attachment #2: Type: text/html, Size: 6225 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/5] efi_loader: bootmgr: install the firmware-owned devicetree
2026-09-08 8:37 ` Ilias Apalodimas
@ 2026-09-08 9:09 ` Carlo Caione
2026-09-08 9:50 ` Ilias Apalodimas
0 siblings, 1 reply; 10+ messages in thread
From: Carlo Caione @ 2026-09-08 9:09 UTC (permalink / raw)
To: Ilias Apalodimas
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Tom Rini, Simon Glass,
Quentin Schulz, Marek Vasut, Johan Jonker, Jonas Karlman,
Randolph Sapp, Daniel Golle, James Hilliard, Aristo Chen,
Peng Fan, Vincent Jardin, Peter Robinson
On Tue, Sep 08, 2026 at 11:37:43 +0100, Ilias Apalodimas wrote:
> Hi Carlo,
Hello Ilias,
> On Thu, 3 Sept 2026 at 16:10, Carlo Caione <ccaione@baylibre.com> wrote:
>
> > SystemReady DeviceTree can boot through the UEFI boot manager without
> > going through the per-device EFI bootmeth. Use firmware_fdt_stage() here
> > as well so both EFI launch paths install the same firmware-owned
> > devicetree.
[...]
> > if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
> > + if (!fdt) {
> > + ulong fdt_addr = env_get_hex("fdt_addr_r", 0);
> > + int err;
> >
> +
> >
>
> Any reason we are doing the _hex variant instead of env_get? Don't we want
> to bail out if the env variable isnt defined instead of picking 0x0 ?
We do, but we do that in firmware_fdt_stage() that is returning -EINVAL
before loading or copying the FIT (so we return EFI_DEVICE_ERROR from
this function in the end).
So basically zero is an invalid-address sentinel, never a staging
destination.
> + err = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
> > + &fw_fdt_fname);
> > + if (!err) {
> > + fdt = map_sysmem(fdt_addr, fw_fdt_size);
> > + } else if (err != -ENOENT) {
> > + log_err("Failed to load firmware
> > devicetree '%s' (err %d)\n",
> > + fw_fdt_fname ? fw_fdt_fname :
> > "fdt.itb", err);
> > + ret = EFI_DEVICE_ERROR;
> > + goto out;
> > + }
> > + }
> > +
[...]
Ciao,
--
Carlo Caione
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/5] efi_loader: bootmgr: install the firmware-owned devicetree
2026-09-08 9:09 ` Carlo Caione
@ 2026-09-08 9:50 ` Ilias Apalodimas
2026-09-08 10:05 ` Carlo Caione
0 siblings, 1 reply; 10+ messages in thread
From: Ilias Apalodimas @ 2026-09-08 9:50 UTC (permalink / raw)
To: Carlo Caione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Tom Rini, Simon Glass,
Quentin Schulz, Marek Vasut, Johan Jonker, Jonas Karlman,
Randolph Sapp, Daniel Golle, James Hilliard, Aristo Chen,
Peng Fan, Vincent Jardin, Peter Robinson
[-- Attachment #1: Type: text/plain, Size: 2156 bytes --]
Hi Carlo,
On Tue, 8 Sept 2026 at 12:09, Carlo Caione <ccaione@baylibre.com> wrote:
> On Tue, Sep 08, 2026 at 11:37:43 +0100, Ilias Apalodimas wrote:
> > Hi Carlo,
>
> Hello Ilias,
>
> > On Thu, 3 Sept 2026 at 16:10, Carlo Caione <ccaione@baylibre.com> wrote:
> >
> > > SystemReady DeviceTree can boot through the UEFI boot manager without
> > > going through the per-device EFI bootmeth. Use firmware_fdt_stage()
> here
> > > as well so both EFI launch paths install the same firmware-owned
> > > devicetree.
>
> [...]
> > > if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
> > > + if (!fdt) {
> > > + ulong fdt_addr = env_get_hex("fdt_addr_r", 0);
> > > + int err;
> > >
> > +
> > >
> >
> > Any reason we are doing the _hex variant instead of env_get? Don't we
> want
> > to bail out if the env variable isnt defined instead of picking 0x0 ?
>
> We do, but we do that in firmware_fdt_stage() that is returning -EINVAL
> before loading or copying the FIT (so we return EFI_DEVICE_ERROR from
> this function in the end).
>
That might end up being problematic. I don't know if any devices do that
today but 0x0 is a valid address.
Can't we get rid of the check in firmware_fdt_stage() and never call it if
we don't have an address set?
Thanks
/Ilias
>
> So basically zero is an invalid-address sentinel, never a staging
> destination.
>
> > + err = firmware_fdt_stage(fdt_addr, &fw_fdt_size,
> > > + &fw_fdt_fname);
> > > + if (!err) {
> > > + fdt = map_sysmem(fdt_addr,
> fw_fdt_size);
> > > + } else if (err != -ENOENT) {
> > > + log_err("Failed to load firmware
> > > devicetree '%s' (err %d)\n",
> > > + fw_fdt_fname ? fw_fdt_fname :
> > > "fdt.itb", err);
> > > + ret = EFI_DEVICE_ERROR;
> > > + goto out;
> > > + }
> > > + }
> > > +
> [...]
>
> Ciao,
>
> --
> Carlo Caione
>
[-- Attachment #2: Type: text/html, Size: 3926 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/5] efi_loader: bootmgr: install the firmware-owned devicetree
2026-09-08 9:50 ` Ilias Apalodimas
@ 2026-09-08 10:05 ` Carlo Caione
0 siblings, 0 replies; 10+ messages in thread
From: Carlo Caione @ 2026-09-08 10:05 UTC (permalink / raw)
To: Ilias Apalodimas
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Tom Rini, Simon Glass,
Quentin Schulz, Marek Vasut, Johan Jonker, Jonas Karlman,
Randolph Sapp, Daniel Golle, James Hilliard, Aristo Chen,
Peng Fan, Vincent Jardin, Peter Robinson
On Tue, Sep 08, 2026 at 12:50:33 +0100, Ilias Apalodimas wrote:
> Hi Carlo,
Hello,
[...]
> > > Any reason we are doing the _hex variant instead of env_get? Don't we
> > want
> > > to bail out if the env variable isnt defined instead of picking 0x0 ?
> >
> > We do, but we do that in firmware_fdt_stage() that is returning -EINVAL
> > before loading or copying the FIT (so we return EFI_DEVICE_ERROR from
> > this function in the end).
> >
>
> That might end up being problematic. I don't know if any devices do that
> today but 0x0 is a valid address.
> Can't we get rid of the check in firmware_fdt_stage() and never call it if
> we don't have an address set?
Ouch, yes, you are right. I guess I'll fix this in V4 then. Thanks for
reviewing this and spotting the problem.
Cheers,
--
Carlo Caione
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-08 10:05 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 2/5] boot: add a firmware-owned devicetree source Carlo Caione
2026-09-03 13:09 ` [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree Carlo Caione
2026-09-03 13:09 ` [PATCH v3 4/5] efi_loader: bootmgr: install " Carlo Caione
2026-09-08 8:37 ` Ilias Apalodimas
2026-09-08 9:09 ` Carlo Caione
2026-09-08 9:50 ` Ilias Apalodimas
2026-09-08 10:05 ` Carlo Caione
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.