* [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-28 18:45 ` Heinrich Schuchardt
2026-09-03 13:09 ` [PATCH v3 2/5] boot: add a firmware-owned devicetree source Carlo Caione
` (6 subsequent siblings)
7 siblings, 1 reply; 26+ 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] 26+ messages in thread* Re: [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-03 13:09 ` [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree Carlo Caione
@ 2026-09-28 18:45 ` Heinrich Schuchardt
2026-09-28 20:33 ` Carlo Caione
0 siblings, 1 reply; 26+ messages in thread
From: Heinrich Schuchardt @ 2026-09-28 18:45 UTC (permalink / raw)
To: Carlo Caione
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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, u-boot
On 9/3/26 15:09, Carlo Caione wrote:
> 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.
Hello Carlo,
I do not understand what you mean by the term "operator".
Do you mean the end user owning the device?
What makes you think a device-tree entry in Boot#### set by the user
owning the device should not take precedence?
I cannot see any justification for the change in the commit message.
The series lacks documentation which describes how device-trees are
selected.
Best regards
Heinrich
>
> 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;
>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-28 18:45 ` Heinrich Schuchardt
@ 2026-09-28 20:33 ` Carlo Caione
2026-09-28 22:00 ` E Shattow
2026-09-28 22:10 ` Heinrich Schuchardt
0 siblings, 2 replies; 26+ messages in thread
From: Carlo Caione @ 2026-09-28 20:33 UTC (permalink / raw)
To: Heinrich Schuchardt
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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, u-boot
On Mon, Sep 28, 2026 at 20:45:37 +0100, Heinrich Schuchardt wrote:
> On 9/3/26 15:09, Carlo Caione wrote:
> > 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.
>
> Hello Carlo,
Hi Heinrich,
> I do not understand what you mean by the term "operator".
> Do you mean the end user owning the device?
Yeah, not a very good choice of words, sorry about that.
By "operator" I mean the user explicitly supplying a DT for that
invocation, for example with `bootefi bootmgr <fdt_addr>`
> What makes you think a device-tree entry in Boot#### set by the user owning
> the device should not take precedence?
>
> I cannot see any justification for the change in the commit message.
The use case is a one-off override: load an alternative DT, for example
for debugging, while using the existing Boot#### entry to select the
EFI application, without modifying its persistent DT setting. Currently,
the explicitly passed DT is silently replaced by the Boot#### DT.
My intention was to make the explicit argument override the stored
setting for that (manual) invocation.
With this patch and patch 4 the full order is:
1. Explicitly passed DT (this fix)
2. Configured firmware-owned DT
3. Boot#### DT
4. Distro/ESP DT
5. Existing `fdt_addr` / control-DT fallback
> The series lacks documentation which describes how device-trees are
> selected.
Yes indeed, I'll document this better in the next revision.
Thanks,
--
Carlo Caione
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-28 20:33 ` Carlo Caione
@ 2026-09-28 22:00 ` E Shattow
2026-10-07 9:30 ` Carlo Caione
2026-09-28 22:10 ` Heinrich Schuchardt
1 sibling, 1 reply; 26+ messages in thread
From: E Shattow @ 2026-09-28 22:00 UTC (permalink / raw)
To: Carlo Caione, Heinrich Schuchardt
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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, u-boot
On 9/28/26 13:33, Carlo Caione wrote:
> On Mon, Sep 28, 2026 at 20:45:37 +0100, Heinrich Schuchardt wrote:
>> On 9/3/26 15:09, Carlo Caione wrote:
>>> 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.
>>
>> Hello Carlo,
>
> Hi Heinrich,
>
>> I do not understand what you mean by the term "operator".
>> Do you mean the end user owning the device?
>
> Yeah, not a very good choice of words, sorry about that.
>
> By "operator" I mean the user explicitly supplying a DT for that
> invocation, for example with `bootefi bootmgr <fdt_addr>`
>
>> What makes you think a device-tree entry in Boot#### set by the user owning
>> the device should not take precedence?
>>
>> I cannot see any justification for the change in the commit message.
>
> The use case is a one-off override: load an alternative DT, for example
> for debugging, while using the existing Boot#### entry to select the
> EFI application, without modifying its persistent DT setting. Currently,
> the explicitly passed DT is silently replaced by the Boot#### DT.
>
> My intention was to make the explicit argument override the stored
> setting for that (manual) invocation.
>
> With this patch and patch 4 the full order is:
>
> 1. Explicitly passed DT (this fix)
> 2. Configured firmware-owned DT
> 3. Boot#### DT
> 4. Distro/ESP DT
> 5. Existing `fdt_addr` / control-DT fallback
>
>> The series lacks documentation which describes how device-trees are
>> selected.
>
> Yes indeed, I'll document this better in the next revision.
>
> Thanks,
>
> --
> Carlo Caione
I understand the need for what you describe, however the method of doing
this is also not clear for me.
When I have encountered this situation the workaround was to invalidate
the 'model' compatible property in the tree to prevent a later match and
whatever process from installing a different Fdt than what I wrote on
the 'bootefi' command invocation in U-Boot.
An example of this is the Debian Linux 'mini.efi' netinst
debian-installer on riscv64 architecture. The 'mini.efi' contains dtb
information for multiple targets, and there is not much configuration
choice about this. It would be useful to suppress the Fdt passing logic
that occurs through EFI, to mask any of the model compatible (possible
using u-boot 'fdt' command), Fdt, and fdtfile name.
I would like to see actual examples of why this series is needed.
-E
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-28 22:00 ` E Shattow
@ 2026-10-07 9:30 ` Carlo Caione
0 siblings, 0 replies; 26+ messages in thread
From: Carlo Caione @ 2026-10-07 9:30 UTC (permalink / raw)
To: E Shattow
Cc: Heinrich Schuchardt, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Ilias Apalodimas, 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, u-boot
On Mon, Sep 28, 2026 at 15:00:03 +0100, E Shattow wrote:
> I understand the need for what you describe, however the method of doing
> this is also not clear for me.
>
> When I have encountered this situation the workaround was to invalidate
> the 'model' compatible property in the tree to prevent a later match and
> whatever process from installing a different Fdt than what I wrote on
> the 'bootefi' command invocation in U-Boot.
>
> An example of this is the Debian Linux 'mini.efi' netinst
> debian-installer on riscv64 architecture. The 'mini.efi' contains dtb
> information for multiple targets, and there is not much configuration
> choice about this. It would be useful to suppress the Fdt passing logic
> that occurs through EFI, to mask any of the model compatible (possible
> using u-boot 'fdt' command), Fdt, and fdtfile name.
>
> I would like to see actual examples of why this series is needed.
Hi E,
The case I want to support with this work is booting an unmodified
generic EFI distribution from SD while supplying the board DT and
overlays from firmware storage on eMMC (this is a very specific use-case
but you get the gist).
The FIT describes the supported combinations and, with required
configuration signatures, authenticates the selected combination
independently of the OS image.
About the mini.efi objection, I think that is the same distinction
previously discussed with Simon for GRUB (see [0]): this series controls
the DT U-Boot initially supplies, but cannot prevent an EFI application
from subsequently replacing it.
Basically Patch 1 addresses replacement inside U-Boot, before the EFI
application starts but it does not force subsequent EFI software to
retain it.
Let me know your thought about this,
cheers!
[0] https://lore.kernel.org/all/CADh8LscEUi5kvGQ28GErjEM=WvQ=6t0p5Kh30jQ-S8ZapG8yrw@mail.gmail.com/
--
Carlo Caione
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 1/5] efi_loader: bootmgr: preserve a passed devicetree
2026-09-28 20:33 ` Carlo Caione
2026-09-28 22:00 ` E Shattow
@ 2026-09-28 22:10 ` Heinrich Schuchardt
1 sibling, 0 replies; 26+ messages in thread
From: Heinrich Schuchardt @ 2026-09-28 22:10 UTC (permalink / raw)
To: Carlo Caione
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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, u-boot
Am 28. September 2026 22:33:06 MESZ schrieb Carlo Caione <ccaione@baylibre.com>:
>On Mon, Sep 28, 2026 at 20:45:37 +0100, Heinrich Schuchardt wrote:
>> On 9/3/26 15:09, Carlo Caione wrote:
>> > 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.
>>
>> Hello Carlo,
>
>Hi Heinrich,
>
>> I do not understand what you mean by the term "operator".
>> Do you mean the end user owning the device?
>
>Yeah, not a very good choice of words, sorry about that.
>
>By "operator" I mean the user explicitly supplying a DT for that
>invocation, for example with `bootefi bootmgr <fdt_addr>`
With the bootefi command mentioned the intent would have been clearer. I agree that an explicitly passed device-tree should take precedence.
>
>> What makes you think a device-tree entry in Boot#### set by the user owning
>> the device should not take precedence?
>>
>> I cannot see any justification for the change in the commit message.
>
>The use case is a one-off override: load an alternative DT, for example
>for debugging, while using the existing Boot#### entry to select the
>EFI application, without modifying its persistent DT setting. Currently,
>the explicitly passed DT is silently replaced by the Boot#### DT.
>
>My intention was to make the explicit argument override the stored
>setting for that (manual) invocation.
>
>With this patch and patch 4 the full order is:
>
>1. Explicitly passed DT (this fix)
>2. Configured firmware-owned DT
>3. Boot#### DT
>4. Distro/ESP DT
>5. Existing `fdt_addr` / control-DT fallback
efi_binary_run() called in do_bootm_efi() is another source of device-trees.
Best regards
Heinrich
>
>> The series lacks documentation which describes how device-trees are
>> selected.
>
>Yes indeed, I'll document this better in the next revision.
>
>Thanks,
>
>--
>Carlo Caione
^ permalink raw reply [flat|nested] 26+ 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-28 17:12 ` Simon Glass
2026-09-03 13:09 ` [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree Carlo Caione
` (5 subsequent siblings)
7 siblings, 1 reply; 26+ 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] 26+ messages in thread* Re: [PATCH v3 2/5] boot: add a firmware-owned devicetree source
2026-09-03 13:09 ` [PATCH v3 2/5] boot: add a firmware-owned devicetree source Carlo Caione
@ 2026-09-28 17:12 ` Simon Glass
0 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:12 UTC (permalink / raw)
To: ccaione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Ilias Apalodimas,
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
Hi Carlo,
On 2026-09-03T13:09:47, Carlo Caione <ccaione@baylibre.com> wrote:
> boot: add a firmware-owned devicetree source
>
> 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 =
> [...]
>
> 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/boot/firmware_fdt.c b/boot/firmware_fdt.c
> @@ -0,0 +1,303 @@
> + /* 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);
This only looks at the environment string, so it could go at the top
of the function, before the FIT is read and every image is hashed.
Then a bad selector is rejected without any I/O.
> diff --git a/boot/firmware_fdt.c b/boot/firmware_fdt.c
> @@ -0,0 +1,303 @@
> + out->fdt = fdt;
> + out->size = len;
> + out->name = filename;
> + out->fdt_owned = fdt_owned;
When fdt_owned is true the assembled devicetree is a separate buffer
and the FIT (up to FIRMWARE_FDT_FIT_MAX_SIZE) is no longer needed, yet
it stays in the cache for the rest of the boot. Could you free
out->fit in that case and only keep it when the devicetree still
points into it?
> diff --git a/boot/firmware_fdt.c b/boot/firmware_fdt.c
> @@ -0,0 +1,303 @@
> + memcpy(map_sysmem(fdt_addr, fw.size), fw.fdt, fw.size);
> + *fdt_sizep = fw.size;
Just to check, the existing bootmeth path reads the ESP devicetree
through fs_read(), which goes through the LMB check when CONFIG_LMB is
enabled, so a reserved region is not overwritten. This memcpy()
bypasses that. Perhaps use lmb_alloc_addr() (or at least document that
the caller is responsible) so the two paths behave the same? What do
you think?
> diff --git a/boot/firmware_fdt_block.c b/boot/firmware_fdt_block.c
> @@ -0,0 +1,143 @@
> +#include <vsprintf.h>
> +#include <dm/device-internal.h>
> +#include <linux/string.h>
Nothing here calls device_probe() or the other internal functions, so
device-internal.h can be dropped.
> diff --git a/boot/firmware_fdt_block.c b/boot/firmware_fdt_block.c
> @@ -0,0 +1,143 @@
> + 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);
This is essentially fs_load_alloc() with a blk_desc instead of an
interface/part string. Could you build the '%d:%d' string from
blk_get_uclass_name(desc->uclass_id) and desc->devnum and call
fs_load_alloc(), rather than duplicating the size-then-read sequence?
It does the max-size check for you too.
> diff --git a/doc/device-tree-bindings/firmware-fdt.txt b/doc/device-tree-bindings/firmware-fdt.txt
> @@ -0,0 +1,160 @@
> +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
Not quite. Driver model only binds children of the root node and of
nodes whose driver scans its children (e.g. simple-bus), so a node
under an arbitrary parent will never be bound. Please can you say it
should be a child of the root node (or of a bus which binds its
children)?
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ 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-28 17:12 ` Simon Glass
2026-09-03 13:09 ` [PATCH v3 4/5] efi_loader: bootmgr: install " Carlo Caione
` (4 subsequent siblings)
7 siblings, 1 reply; 26+ 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] 26+ messages in thread* Re: [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree
2026-09-03 13:09 ` [PATCH v3 3/5] bootmeth: efi: use the firmware-owned devicetree Carlo Caione
@ 2026-09-28 17:12 ` Simon Glass
0 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:12 UTC (permalink / raw)
To: ccaione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Ilias Apalodimas,
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
Hi Carlo,
On 2026-09-03T13:09:47, Carlo Caione <ccaione@baylibre.com> wrote:
> bootmeth: efi: use the firmware-owned devicetree
>
> 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
> @@ -130,6 +131,22 @@ static int distro_efi_try_bootflow_files(struct udevice *dev,
> + 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);
Just to check the fail-closed behaviour here: a few lines above, the
function has already set bflow->state = BOOTFLOWST_READY, and the
existing code never fails after that point (a missing ESP devicetree
just falls back to the built-in one). This adds the first error return
after the state is READY. With 'bootflow scan -a' the errored bootflow
is still recorded, and as far as I can see bootflow_boot() only checks
the state, not bflow->err, so 'bootflow select' / 'bootflow boot'
would run it with fdt_addr still 0 and no BOOTFLOWF_USE_BUILTIN_FDT
flag, which is exactly what the fail-closed policy is meant to
prevent. I suspect the simplest fix is to move the READY assignment
below the staging call (or reset the state on this error path).
> diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
> @@ -279,10 +314,8 @@ static int distro_efi_read_bootflow_net(struct bootflow *bflow)
> - fdt_addr_str = env_get("fdt_addr_r");
> - if (!fdt_addr_str)
> + if (!fdt_addr)
> return log_msg_ret("fdt", -EINVAL);
This quietly changes the network fallback from "fdt_addr_r is unset"
to "fdt_addr_r is zero". It is a different line from the one Ilias
commented on in patch 4, so please make sure the v4 fix covers this
one too, e.g. by keeping the env_get() check before calling
firmware_fdt_stage().
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ 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-28 17:12 ` Simon Glass
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
` (3 subsequent siblings)
7 siblings, 2 replies; 26+ 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] 26+ 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
2026-09-28 17:12 ` Simon Glass
1 sibling, 1 reply; 26+ 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] 26+ 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; 26+ 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] 26+ 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; 26+ 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] 26+ 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; 26+ 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] 26+ 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-28 17:12 ` Simon Glass
1 sibling, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:12 UTC (permalink / raw)
To: ccaione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Ilias Apalodimas,
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
Hi Carlo,
On 2026-09-03T13:09:47, Carlo Caione <ccaione@baylibre.com> wrote:
> efi_loader: bootmgr: install the firmware-owned devicetree
>
> 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
> @@ -1320,13 +1325,30 @@ efi_status_t efi_bootmgr_run(void *fdt)
> + log_err("Failed to load firmware devicetree '%s' (err %d)\n",
> + fw_fdt_fname ? fw_fdt_fname : "fdt.itb", err);
This duplicates FW_FDT_FILENAME from firmware_fdt.c, which the cover
letter says v3 set out to avoid. It can also be wrong: fw_fdt_fname is
only NULL here when the source's probe fails (or on -ENOMEM), i.e.
before firmware_fdt_stage() has read the 'filename' property, so a
node with a non-default filename is reported as fdt.itb. I suspect it
is better to report the error without a name in that case, or have
firmware_fdt_stage() fill in @namep before probing the device so it is
always available when a source exists.
> diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> @@ -1339,11 +1361,14 @@ efi_status_t efi_bootmgr_run(void *fdt)
> +out:
> if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) {
> + /* efi_install_fdt() has copied the devicetree */
This comment is now above a label also reached from the two error
paths, where efi_install_fdt() has not run. Perhaps 'the devicetree is
no longer needed' or similar, or move the comment next to the
efi_install_fdt() call.
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ 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
2026-09-28 17:12 ` Simon Glass
2026-09-22 8:05 ` [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (2 subsequent siblings)
7 siblings, 1 reply; 26+ 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] 26+ messages in thread* Re: [PATCH v3 5/5] test: boot: add firmware-FDT source tests
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
@ 2026-09-28 17:12 ` Simon Glass
0 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:12 UTC (permalink / raw)
To: ccaione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Ilias Apalodimas,
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
Hi Carlo,
On 2026-09-03T13:09:47, Carlo Caione <ccaione@baylibre.com> wrote:
> test: boot: add firmware-FDT source tests
>
> 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
> [...]
>
> 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/test/boot/firmware_fdt.c b/test/boot/firmware_fdt.c
> @@ -0,0 +1,498 @@
> + ut_assertok(os_persistent_file(fname, sizeof(fname), "mmc11.img"));
> + root = oftree_root(oftree_default());
> + ut_assertok(ofnode_add_subnode(root, "mmc11", &mmc));
This clashes with the BLS series which landed in -next after your base
commit: test.dts now has a (disabled) /mmc11 node and
setup_bls_prefix_image() writes mmc11.img in persistent_data_dir. On
the current tree ofnode_add_subnode() returns -EEXIST here, and even
if it didn't, setup_bls_prefix_image() runs after
setup_firmware_fdt_image() in test_ut_dm_init_bootstd() and overwrites
the image with the ext4 BLS disk. Please can you move to mmc12 (node
name, image and 'mmc_dev') for v4, and perhaps add the node to
test.dts with status 'disabled' like mmc10/mmc11 so the number is
reserved?
> diff --git a/test/boot/firmware_fdt.c b/test/boot/firmware_fdt.c
> @@ -0,0 +1,498 @@
> +/* 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);
This only runs when the test reaches its end. If, say,
firmware_fdt_test_select() fails an assertion, fw_fdt_config stays set
to conf-base and every later test in the file gets a different
configuration from the one it expects, which makes the failure hard to
diagnose. Since the FDT snapshot is restored automatically but the
environment is not, I think it is better to clear both variables at
the start (e.g. in fwfdt_configure()).
> diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
> @@ -698,6 +698,188 @@ def setup_rauc_image(ubman):
> + # GPT with two same-type firmware partitions; the FAT goes in partition 1
The image creates an A/B layout but only partition 1 has a filesystem,
so fw_fdt_part is only ever exercised with a non-existent partition
('9'). Since the A/B override is the main reason that variable exists,
it would be good to put a second FAT in partition 2 (perhaps with a
base DTB carrying a distinguishing property) and check that
fw_fdt_part=2 selects it, and that 1 gives the same result as the
default. A non-numeric selector returning -EINVAL from the dectoul()
check would be worth covering too.
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (4 preceding siblings ...)
2026-09-03 13:09 ` [PATCH v3 5/5] test: boot: add firmware-FDT source tests Carlo Caione
@ 2026-09-22 8:05 ` Carlo Caione
2026-09-28 17:14 ` Simon Glass
2026-09-28 17:12 ` [v3, " Simon Glass
2026-09-28 22:10 ` [PATCH v3 " Heinrich Schuchardt
7 siblings, 1 reply; 26+ messages in thread
From: Carlo Caione @ 2026-09-22 8:05 UTC (permalink / raw)
To: u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Heinrich Schuchardt, Ilias Apalodimas, 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 Thu, Sep 03, 2026 at 15:09:47 +0100, Carlo Caione wrote:
> 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.
Hi Simon,
before pushing v4 to address Ilias comments do you have anything for me
to fix on your side?
Thanks!
--
Carlo Caione
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-22 8:05 ` [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
@ 2026-09-28 17:14 ` Simon Glass
0 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:14 UTC (permalink / raw)
To: Carlo Caione
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Heinrich Schuchardt, Ilias Apalodimas,
Tom Rini, Quentin Schulz, Marek Vasut, Johan Jonker,
Jonas Karlman, Randolph Sapp, Daniel Golle, James Hilliard,
Aristo Chen, Peng Fan, Vincent Jardin, Peter Robinson
Hi Carlo,
On Tue, 22 Sept 2026 at 02:05, Carlo Caione <ccaione@baylibre.com> wrote:
>
> On Thu, Sep 03, 2026 at 15:09:47 +0100, Carlo Caione wrote:
> > 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.
>
> Hi Simon,
> before pushing v4 to address Ilias comments do you have anything for me
> to fix on your side?
I've sent some more thoughts. I'll keep an eye out for v5, patchwork permitting.
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [v3, 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (5 preceding siblings ...)
2026-09-22 8:05 ` [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
@ 2026-09-28 17:12 ` Simon Glass
2026-09-28 22:10 ` [PATCH v3 " Heinrich Schuchardt
7 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2026-09-28 17:12 UTC (permalink / raw)
To: ccaione; +Cc: u-boot
Hi Carlo,
On 2026-09-03T13:09:47, Carlo Caione <ccaione@baylibre.com> wrote:
> before pushing v4 to address Ilias comments do you have anything for me
> to fix on your side?
Thanks for the ping. The design looks good and the patch split is
sensible. A few series-level things here, with per-patch comments to
follow.
> Patch 5 adds sandbox coverage.
The tests only exercise firmware_fdt_load() and firmware_fdt_stage()
directly, so patches 3 and 4 have no coverage. Since the fail-closed
policy lives in those two patches, it needs a test. The existing
bootflow_efi test in test/boot/bootflow.c could be extended (or a
sibling added) which creates the source node at runtime, as the new
tests already do, then checks that 'bootflow list' or 'bootflow info'
reports the FIT filename and that a corrupt FIT stops the bootflow
rather than falling back to the ESP devicetree. Something similar for
the boot-manager path would be good too: since the bootmeth_efi_mgr
path calls efi_bootmgr_run(EFI_FDT_USE_INTERNAL), a test could
configure the source, run the efi_mgr bootmeth (or 'bootefi bootmgr')
and check the installed configuration table, plus a fail-closed case
which checks for EFI_DEVICE_ERROR.
> Both the EFI bootmeth and EFI boot manager use the same staging helper.
Just to check, the plain 'bootefi <addr>' path (without a devicetree
argument) still installs the control devicetree and never consults the
firmware source, so the fail-closed guarantee only holds for the
bootmeth and boot-manager paths. Is that intended? If so it is worth
saying in the documentation, since someone reading the secure-boot
section would probably assume it applies to every EFI launch path.
> The loader does not depend on standard boot. EFI is its first consumer,
> but the source drivers, FIT handling and staging interface are generic.
Given that, doc/develop/uefi/ seems an odd home for the documentation,
since nothing in the loader is EFI-specific. Perhaps the generic parts
(uclass, FIT contract, selectors, caching) could go under doc/develop/
with just the EFI-consumer section staying in the UEFI docs? Also, the
test is built under CONFIG_UT_BOOTSTD and uses BOOTSTD_TEST even
though the loader is independent of bootstd. Neither is a big deal,
but the placement does not match the description. What do you think?
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-03 13:09 [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree Carlo Caione
` (6 preceding siblings ...)
2026-09-28 17:12 ` [v3, " Simon Glass
@ 2026-09-28 22:10 ` Heinrich Schuchardt
2026-09-28 22:20 ` Heinrich Schuchardt
2026-10-07 12:02 ` Carlo Caione
7 siblings, 2 replies; 26+ messages in thread
From: Heinrich Schuchardt @ 2026-09-28 22:10 UTC (permalink / raw)
To: Carlo Caione, u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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
Am 3. September 2026 15:09:47 MESZ schrieb Carlo Caione <ccaione@baylibre.com>:
>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;
>
Why should a "firmware-owned" device-tree live on an OS owned block device?
This lack of separation of duties is bound to lead to total failure once I reinstall my OS which typically starts with repartitioning the block device.
Please, put anything that is firmware owned on a storage device that is firmware owned and not OS owned.
>- '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,
The topic of constructing a firmware device-tree seems to be completely independent of the way the boot binary is invoked.
The easiest way to make such a device-tree available for other boot methods would be providing an environment variable that can be used instead of $fdtcontroladdr.
The construction should only occur once and not repeatedly for each of the EFI binaries that is executed.
Best regards
Heinrich
>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] 26+ messages in thread* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-28 22:10 ` [PATCH v3 " Heinrich Schuchardt
@ 2026-09-28 22:20 ` Heinrich Schuchardt
2026-10-07 12:07 ` Carlo Caione
2026-10-07 12:02 ` Carlo Caione
1 sibling, 1 reply; 26+ messages in thread
From: Heinrich Schuchardt @ 2026-09-28 22:20 UTC (permalink / raw)
To: Carlo Caione, u-boot
Cc: Ahmad Fatoum, David Lechner, Julien Masson, Vitor Sato Eschholz,
Ilias Apalodimas, 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
Am 29. September 2026 00:10:05 MESZ schrieb Heinrich Schuchardt <xypron.glpk@gmx.de>:
>Am 3. September 2026 15:09:47 MESZ schrieb Carlo Caione <ccaione@baylibre.com>:
>>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;
>>
>
>Why should a "firmware-owned" device-tree live on an OS owned block device?
>
>This lack of separation of duties is bound to lead to total failure once I reinstall my OS which typically starts with repartitioning the block device.
>
>Please, put anything that is firmware owned on a storage device that is firmware owned and not OS owned.
>
>>- '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,
>
>The topic of constructing a firmware device-tree seems to be completely independent of the way the boot binary is invoked.
>
>The easiest way to make such a device-tree available for other boot methods would be providing an environment variable that can be used instead of $fdtcontroladdr.
>
>The construction should only occur once and not repeatedly for each of the EFI binaries that is executed.
Thinking of other boot stages like EDK II the device-tree may already have to be created in U-Boot SPL.
Multiple RISC-V systems boot via
U-Boot SPL -> OpenSBI-> EDK II
Best regards
Heinrich
>>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] 26+ messages in thread* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-28 22:20 ` Heinrich Schuchardt
@ 2026-10-07 12:07 ` Carlo Caione
0 siblings, 0 replies; 26+ messages in thread
From: Carlo Caione @ 2026-10-07 12:07 UTC (permalink / raw)
To: Heinrich Schuchardt
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Ilias Apalodimas, 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 29, 2026 at 00:20:41 +0100, Heinrich Schuchardt wrote:
> Thinking of other boot stages like EDK II the device-tree may already
> have to be created in U-Boot SPL.
>
> Multiple RISC-V systems boot via
>
> U-Boot SPL -> OpenSBI-> EDK II
Agreed: that chain has no U-Boot proper, so the EFI hooks in this series
never run. If U-Boot prepares the DT, it would need to do so in SPL and
hand it onward but, yes, this series does not yet enable it in SPL.
Cheers,
--
Carlo Caione
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v3 0/5] firmware-owned devicetree for EBBR / SystemReady DeviceTree
2026-09-28 22:10 ` [PATCH v3 " Heinrich Schuchardt
2026-09-28 22:20 ` Heinrich Schuchardt
@ 2026-10-07 12:02 ` Carlo Caione
1 sibling, 0 replies; 26+ messages in thread
From: Carlo Caione @ 2026-10-07 12:02 UTC (permalink / raw)
To: Heinrich Schuchardt
Cc: u-boot, Ahmad Fatoum, David Lechner, Julien Masson,
Vitor Sato Eschholz, Ilias Apalodimas, 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 29, 2026 at 00:10:05 +0100, Heinrich Schuchardt wrote:
Hi Heinrich,
> Why should a "firmware-owned" device-tree live on an OS owned block
> device?
>
> This lack of separation of duties is bound to lead to total failure
> once I reinstall my OS which typically starts with repartitioning the
> block device.
>
> Please, put anything that is firmware owned on a storage device that
> is firmware owned and not OS owned.
I mean, we are really not enforcing anything here. The block backend
does not require the firmware DT to reside on the OS device: the source
explicitly references its backing device, independently of where the OS
boots from.
That said, you are right that sharing an OS-repartitionable disk leaves
the firmware partitions vulnerable, but my idea is that the platform
must arrange storage ownership/protection so that replacing the OS does
not destroy firmware data.
As I said in my previous reply to E, think about booting an OS image
from SD while the DT is shipped at provision time on the eMMC.
> The topic of constructing a firmware device-tree seems to be
> completely independent of the way the boot binary is invoked.
>
> The easiest way to make such a device-tree available for other boot
> methods would be providing an environment variable that can be used
> instead of $fdtcontroladdr.
>
> The construction should only occur once and not repeatedly for each of
> the EFI binaries that is executed.
ok, not in order: DT construction is already independent of EFI through
firmware_fdt_load(), the EFI paths are its first consumers. Successful
loading, verification and assembly are cached, so they repeat only when
the partition/configuration selectors change.
Now about the new environment variable, that is something I thought
about and I'm open to it if the general consensus is that it is a better
way forward for this problem.
The idea would be that we introduce a preparation step that loads
`fdt.itb`, verifies it, applies overlays, and publishes the resulting
address in an environment variable. The preparation step would also
return a status, so callers can distinguish an absent source from a
configured source that failed to load or verify. I'm probably missing
100 complications but this is the basic idea, just to check if we are on
the same page about this.
Thanks,
--
Carlo Caione
^ permalink raw reply [flat|nested] 26+ messages in thread